rawsql-ts
Version:
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
432 lines • 18 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AlterTableParser = void 0;
const SqlTokenizer_1 = require("./SqlTokenizer");
const DDLStatements_1 = require("../models/DDLStatements");
const CreateTableQuery_1 = require("../models/CreateTableQuery");
const Lexeme_1 = require("../models/Lexeme");
const FullNameParser_1 = require("./FullNameParser");
const ValueComponent_1 = require("../models/ValueComponent");
const ValueParser_1 = require("./ValueParser");
const ParserStringUtils_1 = require("../utils/ParserStringUtils");
/**
* Parses ALTER TABLE statements focused on constraint operations.
*/
class AlterTableParser {
static parse(sql) {
const tokenizer = new SqlTokenizer_1.SqlTokenizer(sql);
const lexemes = tokenizer.readLexemes();
const result = this.parseFromLexeme(lexemes, 0);
if (result.newIndex < lexemes.length) {
throw new Error(`[AlterTableParser] Unexpected token "${lexemes[result.newIndex].value}" after ALTER TABLE statement.`);
}
return result.value;
}
static parseFromLexeme(lexemes, index) {
var _a, _b, _c, _d;
let idx = index;
if (((_a = lexemes[idx]) === null || _a === void 0 ? void 0 : _a.value.toLowerCase()) !== "alter table") {
throw new Error(`[AlterTableParser] Expected ALTER TABLE at index ${idx}.`);
}
idx++;
let ifExists = false;
if (((_b = lexemes[idx]) === null || _b === void 0 ? void 0 : _b.value.toLowerCase()) === "if exists") {
ifExists = true;
idx++;
}
let only = false;
if (((_c = lexemes[idx]) === null || _c === void 0 ? void 0 : _c.value.toLowerCase()) === "only") {
only = true;
idx++;
}
const tableResult = FullNameParser_1.FullNameParser.parseFromLexeme(lexemes, idx);
const tableName = new ValueComponent_1.QualifiedName(tableResult.namespaces, tableResult.name);
idx = tableResult.newIndex;
const actions = [];
while (idx < lexemes.length) {
const value = lexemes[idx].value.toLowerCase();
if (value === "add constraint") {
const result = this.parseAddConstraintAction(lexemes, idx);
actions.push(result.value);
idx = result.newIndex;
}
else if (value === "drop constraint") {
const result = this.parseDropConstraintAction(lexemes, idx);
actions.push(result.value);
idx = result.newIndex;
}
else if (value === "drop column" || value === "drop") {
const result = this.parseDropColumnAction(lexemes, idx);
actions.push(result.value);
idx = result.newIndex;
}
else {
throw new Error(`[AlterTableParser] Unsupported ALTER TABLE action '${lexemes[idx].value}' at index ${idx}.`);
}
if (((_d = lexemes[idx]) === null || _d === void 0 ? void 0 : _d.type) === Lexeme_1.TokenType.Comma) {
idx++;
continue;
}
break;
}
if (actions.length === 0) {
throw new Error("[AlterTableParser] ALTER TABLE requires at least one action.");
}
return {
value: new DDLStatements_1.AlterTableStatement({ table: tableName, only, ifExists, actions }),
newIndex: idx
};
}
static parseAddConstraintAction(lexemes, index) {
var _a, _b, _c, _d, _e;
let idx = index;
const initialToken = (_a = lexemes[idx]) === null || _a === void 0 ? void 0 : _a.value.toLowerCase();
if (initialToken !== "add" && initialToken !== "add constraint") {
throw new Error(`[AlterTableParser] Expected ADD or ADD CONSTRAINT at index ${idx}.`);
}
idx++;
// If the token was plain ADD, consume optional CONSTRAINT keyword.
if (initialToken === "add" && ((_b = lexemes[idx]) === null || _b === void 0 ? void 0 : _b.value.toLowerCase()) === "constraint") {
idx++;
}
let ifNotExists = false;
if (((_c = lexemes[idx]) === null || _c === void 0 ? void 0 : _c.value.toLowerCase()) === "if not exists") {
ifNotExists = true;
idx++;
}
let constraintName;
const nextValue = (_d = lexemes[idx]) === null || _d === void 0 ? void 0 : _d.value.toLowerCase();
if (nextValue && !this.CONSTRAINT_TYPE_TOKENS.has(nextValue)) {
const nameResult = FullNameParser_1.FullNameParser.parseFromLexeme(lexemes, idx);
constraintName = nameResult.name;
idx = nameResult.newIndex;
}
const constraintResult = this.parseTableConstraintDefinition(lexemes, idx, constraintName);
idx = constraintResult.newIndex;
let notValid = false;
if (((_e = lexemes[idx]) === null || _e === void 0 ? void 0 : _e.value.toLowerCase()) === "not valid") {
notValid = true;
idx++;
}
return {
value: new DDLStatements_1.AlterTableAddConstraint({
constraint: constraintResult.constraint,
ifNotExists,
notValid
}),
newIndex: idx
};
}
static parseDropConstraintAction(lexemes, index) {
var _a, _b, _c, _d;
let idx = index;
const initialValue = (_a = lexemes[idx]) === null || _a === void 0 ? void 0 : _a.value.toLowerCase();
if (initialValue === "drop constraint") {
idx++;
}
else if (initialValue === "drop") {
idx++;
if (((_b = lexemes[idx]) === null || _b === void 0 ? void 0 : _b.value.toLowerCase()) !== "constraint") {
throw new Error(`[AlterTableParser] Expected CONSTRAINT keyword after DROP at index ${idx}.`);
}
idx++;
}
else {
throw new Error(`[AlterTableParser] Expected DROP CONSTRAINT at index ${idx}.`);
}
let ifExists = false;
if (((_c = lexemes[idx]) === null || _c === void 0 ? void 0 : _c.value.toLowerCase()) === "if exists") {
ifExists = true;
idx++;
}
const nameResult = FullNameParser_1.FullNameParser.parseFromLexeme(lexemes, idx);
idx = nameResult.newIndex;
let behavior = null;
const nextValue = (_d = lexemes[idx]) === null || _d === void 0 ? void 0 : _d.value.toLowerCase();
if (nextValue === "cascade" || nextValue === "restrict") {
behavior = nextValue;
idx++;
}
return {
value: new DDLStatements_1.AlterTableDropConstraint({
constraintName: nameResult.name,
ifExists,
behavior
}),
newIndex: idx
};
}
static parseDropColumnAction(lexemes, index) {
var _a, _b, _c, _d;
let idx = index;
const initialValue = (_a = lexemes[idx]) === null || _a === void 0 ? void 0 : _a.value.toLowerCase();
if (initialValue === "drop column") {
idx++;
}
else if (initialValue === "drop") {
idx++;
if (((_b = lexemes[idx]) === null || _b === void 0 ? void 0 : _b.value.toLowerCase()) !== "column") {
throw new Error(`[AlterTableParser] Expected COLUMN keyword after DROP at index ${idx}.`);
}
idx++;
}
else {
throw new Error(`[AlterTableParser] Expected DROP COLUMN at index ${idx}.`);
}
let ifExists = false;
if (((_c = lexemes[idx]) === null || _c === void 0 ? void 0 : _c.value.toLowerCase()) === "if exists") {
// Accept optional IF EXISTS modifier for defensive migrations.
ifExists = true;
idx++;
}
// Parse the column identifier, propagating any attached comments.
const nameResult = FullNameParser_1.FullNameParser.parseFromLexeme(lexemes, idx);
const columnName = nameResult.name;
idx = nameResult.newIndex;
let behavior = null;
const nextValue = (_d = lexemes[idx]) === null || _d === void 0 ? void 0 : _d.value.toLowerCase();
if (nextValue === "cascade" || nextValue === "restrict") {
// Capture optional drop behavior to mirror PostgreSQL semantics.
behavior = nextValue;
idx++;
}
return {
value: new DDLStatements_1.AlterTableDropColumn({
columnName,
ifExists,
behavior
}),
newIndex: idx
};
}
static parseTableConstraintDefinition(lexemes, index, constraintName) {
let idx = index;
const token = lexemes[idx];
if (!token) {
throw new Error(`[AlterTableParser] Missing constraint definition at index ${idx}.`);
}
const value = token.value.toLowerCase();
if (value === "primary key") {
idx++;
const listResult = this.parseIdentifierList(lexemes, idx);
idx = listResult.newIndex;
return {
constraint: new CreateTableQuery_1.TableConstraintDefinition({
kind: "primary-key",
constraintName,
columns: listResult.identifiers
}),
newIndex: idx
};
}
if (value === "unique" || value === "unique key") {
idx++;
const listResult = this.parseIdentifierList(lexemes, idx);
idx = listResult.newIndex;
return {
constraint: new CreateTableQuery_1.TableConstraintDefinition({
kind: "unique",
constraintName,
columns: listResult.identifiers
}),
newIndex: idx
};
}
if (value === "foreign key") {
idx++;
const listResult = this.parseIdentifierList(lexemes, idx);
idx = listResult.newIndex;
const referenceResult = this.parseReferenceDefinition(lexemes, idx);
idx = referenceResult.newIndex;
return {
constraint: new CreateTableQuery_1.TableConstraintDefinition({
kind: "foreign-key",
constraintName,
columns: listResult.identifiers,
reference: referenceResult.reference,
deferrable: referenceResult.reference.deferrable,
initially: referenceResult.reference.initially
}),
newIndex: idx
};
}
if (value === "check") {
idx++;
const checkExpression = this.parseParenExpression(lexemes, idx);
idx = checkExpression.newIndex;
return {
constraint: new CreateTableQuery_1.TableConstraintDefinition({
kind: "check",
constraintName,
checkExpression: checkExpression.value
}),
newIndex: idx
};
}
const rawEnd = this.findConstraintClauseEnd(lexemes, idx + 1);
const rawText = (0, ParserStringUtils_1.joinLexemeValues)(lexemes, idx, rawEnd);
return {
constraint: new CreateTableQuery_1.TableConstraintDefinition({
kind: "raw",
constraintName,
rawClause: new ValueComponent_1.RawString(rawText)
}),
newIndex: rawEnd
};
}
static parseIdentifierList(lexemes, index) {
var _a, _b, _c;
let idx = index;
const identifiers = [];
if (((_a = lexemes[idx]) === null || _a === void 0 ? void 0 : _a.type) !== Lexeme_1.TokenType.OpenParen) {
throw new Error(`[AlterTableParser] Expected '(' to start identifier list at index ${idx}.`);
}
idx++;
while (idx < lexemes.length) {
const nameResult = FullNameParser_1.FullNameParser.parseFromLexeme(lexemes, idx);
identifiers.push(nameResult.name);
idx = nameResult.newIndex;
if (((_b = lexemes[idx]) === null || _b === void 0 ? void 0 : _b.type) === Lexeme_1.TokenType.Comma) {
idx++;
continue;
}
if (((_c = lexemes[idx]) === null || _c === void 0 ? void 0 : _c.type) === Lexeme_1.TokenType.CloseParen) {
idx++;
break;
}
}
return { identifiers, newIndex: idx };
}
static parseReferenceDefinition(lexemes, index) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
let idx = index;
if (((_a = lexemes[idx]) === null || _a === void 0 ? void 0 : _a.value.toLowerCase()) !== "references") {
throw new Error(`[AlterTableParser] Expected REFERENCES clause at index ${idx}.`);
}
idx++;
const tableResult = FullNameParser_1.FullNameParser.parseFromLexeme(lexemes, idx);
const targetTable = new ValueComponent_1.QualifiedName(tableResult.namespaces, tableResult.name);
idx = tableResult.newIndex;
let columns = null;
if (((_b = lexemes[idx]) === null || _b === void 0 ? void 0 : _b.type) === Lexeme_1.TokenType.OpenParen) {
const listResult = this.parseIdentifierList(lexemes, idx);
columns = listResult.identifiers;
idx = listResult.newIndex;
}
let matchType = null;
let onDelete = null;
let onUpdate = null;
let deferrable = null;
let initially = null;
while (idx < lexemes.length) {
const current = lexemes[idx].value.toLowerCase();
if (this.MATCH_KEYWORDS.has(current)) {
matchType = this.MATCH_KEYWORDS.get(current);
idx++;
continue;
}
if (current === "match") {
idx++;
const descriptor = (_d = (_c = lexemes[idx]) === null || _c === void 0 ? void 0 : _c.value.toLowerCase()) !== null && _d !== void 0 ? _d : "";
matchType = descriptor;
idx++;
continue;
}
if (current === "on delete") {
idx++;
const action = (_f = (_e = lexemes[idx]) === null || _e === void 0 ? void 0 : _e.value.toLowerCase()) !== null && _f !== void 0 ? _f : "";
onDelete = (_g = this.REFERENTIAL_ACTIONS.get(action)) !== null && _g !== void 0 ? _g : null;
idx++;
continue;
}
if (current === "on update") {
idx++;
const action = (_j = (_h = lexemes[idx]) === null || _h === void 0 ? void 0 : _h.value.toLowerCase()) !== null && _j !== void 0 ? _j : "";
onUpdate = (_k = this.REFERENTIAL_ACTIONS.get(action)) !== null && _k !== void 0 ? _k : null;
idx++;
continue;
}
if (this.DEFERRABILITY_KEYWORDS.has(current)) {
deferrable = this.DEFERRABILITY_KEYWORDS.get(current);
idx++;
continue;
}
if (this.INITIALLY_KEYWORDS.has(current)) {
initially = this.INITIALLY_KEYWORDS.get(current);
idx++;
continue;
}
break;
}
return {
reference: new CreateTableQuery_1.ReferenceDefinition({
targetTable,
columns,
matchType,
onDelete,
onUpdate,
deferrable,
initially
}),
newIndex: idx
};
}
static parseParenExpression(lexemes, index) {
var _a, _b;
let idx = index;
if (((_a = lexemes[idx]) === null || _a === void 0 ? void 0 : _a.type) !== Lexeme_1.TokenType.OpenParen) {
throw new Error(`[AlterTableParser] Expected '(' starting CHECK expression at index ${idx}.`);
}
idx++;
const result = ValueParser_1.ValueParser.parseFromLexeme(lexemes, idx);
idx = result.newIndex;
if (((_b = lexemes[idx]) === null || _b === void 0 ? void 0 : _b.type) !== Lexeme_1.TokenType.CloseParen) {
throw new Error(`[AlterTableParser] Expected ')' closing CHECK expression at index ${idx}.`);
}
idx++;
return { value: result.value, newIndex: idx };
}
static findConstraintClauseEnd(lexemes, index) {
let idx = index;
while (idx < lexemes.length) {
const token = lexemes[idx];
if (token.type & (Lexeme_1.TokenType.Comma | Lexeme_1.TokenType.CloseParen)) {
break;
}
if (token.value.toLowerCase() === "not valid") {
break;
}
idx++;
}
return idx;
}
}
exports.AlterTableParser = AlterTableParser;
AlterTableParser.CONSTRAINT_TYPE_TOKENS = new Set([
"primary key",
"unique",
"unique key",
"foreign key",
"check"
]);
AlterTableParser.MATCH_KEYWORDS = new Map([
["match full", "full"],
["match partial", "partial"],
["match simple", "simple"]
]);
AlterTableParser.REFERENTIAL_ACTIONS = new Map([
["cascade", "cascade"],
["restrict", "restrict"],
["no action", "no action"],
["set null", "set null"],
["set default", "set default"]
]);
AlterTableParser.DEFERRABILITY_KEYWORDS = new Map([
["deferrable", "deferrable"],
["not deferrable", "not deferrable"]
]);
AlterTableParser.INITIALLY_KEYWORDS = new Map([
["initially immediate", "immediate"],
["initially deferred", "deferred"]
]);
//# sourceMappingURL=AlterTableParser.js.map