UNPKG

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
"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