UNPKG

rawsql-ts

Version:

High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.

279 lines 14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InsertQueryParser = void 0; // filepath: src/parsers/InsertQueryParser.ts // Provides parsing for INSERT queries, supporting optional columns and WITH/SELECT/VALUES structure. const InsertQuery_1 = require("../models/InsertQuery"); const Lexeme_1 = require("../models/Lexeme"); const SqlTokenizer_1 = require("./SqlTokenizer"); const SelectQueryParser_1 = require("./SelectQueryParser"); const Clause_1 = require("../models/Clause"); const SelectQueryWithClauseHelper_1 = require("../utils/SelectQueryWithClauseHelper"); const WithClauseParser_1 = require("./WithClauseParser"); const SourceExpressionParser_1 = require("./SourceExpressionParser"); const ValuesQueryParser_1 = require("./ValuesQueryParser"); const ReturningClauseParser_1 = require("./ReturningClauseParser"); const ValueComponent_1 = require("../models/ValueComponent"); const LexemeCommentUtils_1 = require("./utils/LexemeCommentUtils"); const ParserStringUtils_1 = require("../utils/ParserStringUtils"); const ForClauseParser_1 = require("./ForClauseParser"); const WhereClauseParser_1 = require("./WhereClauseParser"); const SetClauseParser_1 = require("./SetClauseParser"); class InsertQueryParser { /** * Parse SQL string to InsertQuery AST. * @param query SQL string */ static parse(query) { const tokenizer = new SqlTokenizer_1.SqlTokenizer(query); const lexemes = tokenizer.readLexemes(); const result = this.parseFromLexeme(lexemes, 0); if (result.newIndex < lexemes.length) { throw new Error(`Syntax error: Unexpected token "${lexemes[result.newIndex].value}" at position ${result.newIndex}. The INSERT query is complete but there are additional tokens.`); } return result.value; } /** * Parse from lexeme array (for internal use and tests) */ static parseFromLexeme(lexemes, index) { var _a, _b, _c, _d, _e, _f, _g, _h; let idx = index; let withClause = null; if (((_a = lexemes[idx]) === null || _a === void 0 ? void 0 : _a.value) === "with") { const result = WithClauseParser_1.WithClauseParser.parseFromLexeme(lexemes, idx); withClause = result.value; idx = result.newIndex; } if (!lexemes[idx] || lexemes[idx].value !== "insert into") { const found = (_c = (_b = lexemes[idx]) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : "end of input"; throw new Error(`Syntax error at position ${idx}: Expected 'INSERT INTO' but found '${found}'.`); } const insertKeywordLexeme = lexemes[idx]; const insertKeywordComments = (0, LexemeCommentUtils_1.extractLexemeComments)(insertKeywordLexeme); idx++; const sourceResult = SourceExpressionParser_1.SourceExpressionParser.parseTableSourceFromLexemes(lexemes, idx); const targetSource = sourceResult.value; const targetDatasource = targetSource.datasource; idx = sourceResult.newIndex; // Route inline comments immediately following INSERT INTO to the table reference. if (insertKeywordComments.after.length > 0) { targetDatasource.addPositionedComments("before", insertKeywordComments.after); } let columnIdentifiers = null; let trailingInsertComments = []; if (((_d = lexemes[idx]) === null || _d === void 0 ? void 0 : _d.type) === Lexeme_1.TokenType.OpenParen) { const openParenLexeme = lexemes[idx]; const parenComments = (0, LexemeCommentUtils_1.extractLexemeComments)(openParenLexeme); idx++; if (parenComments.before.length > 0) { // Comments before '(' belong after the table name. targetDatasource.addPositionedComments("after", parenComments.before); } columnIdentifiers = []; let pendingBeforeForNext = [...parenComments.after]; while (idx < lexemes.length && (lexemes[idx].type & Lexeme_1.TokenType.Identifier)) { const columnLexeme = lexemes[idx]; const columnComments = (0, LexemeCommentUtils_1.extractLexemeComments)(columnLexeme); const column = new ValueComponent_1.IdentifierString(columnLexeme.value); // Attach leading comments gathered from preceding tokens and the identifier itself. const beforeComments = []; if (pendingBeforeForNext.length > 0) { beforeComments.push(...pendingBeforeForNext); } if (columnComments.before.length > 0) { beforeComments.push(...columnComments.before); } if (beforeComments.length > 0) { column.addPositionedComments("before", beforeComments); } if (columnComments.after.length > 0) { column.addPositionedComments("after", columnComments.after); } columnIdentifiers.push(column); pendingBeforeForNext = []; idx++; if (((_e = lexemes[idx]) === null || _e === void 0 ? void 0 : _e.type) === Lexeme_1.TokenType.Comma) { const commaComments = (0, LexemeCommentUtils_1.extractLexemeComments)(lexemes[idx]); pendingBeforeForNext = [...commaComments.after]; idx++; continue; } break; } if (pendingBeforeForNext.length > 0 && columnIdentifiers.length > 0) { columnIdentifiers[columnIdentifiers.length - 1].addPositionedComments("after", pendingBeforeForNext); pendingBeforeForNext = []; } if (((_f = lexemes[idx]) === null || _f === void 0 ? void 0 : _f.type) !== Lexeme_1.TokenType.CloseParen) { throw new Error(`Syntax error at position ${idx}: Expected ')' after column list.`); } const closeParenComments = (0, LexemeCommentUtils_1.extractLexemeComments)(lexemes[idx]); idx++; if (closeParenComments.before.length > 0 && columnIdentifiers.length > 0) { columnIdentifiers[columnIdentifiers.length - 1].addPositionedComments("after", closeParenComments.before); } if (closeParenComments.after.length > 0) { // Comments after ')' should trail the entire INSERT clause. trailingInsertComments = closeParenComments.after; } if (columnIdentifiers.length === 0) { columnIdentifiers = []; } } if (idx >= lexemes.length) { throw new Error(`Syntax error: Unexpected end of input while parsing INSERT statement. VALUES or SELECT clause expected.`); } const nextToken = lexemes[idx].value.toLowerCase(); let dataQuery; if (nextToken === "values") { const valuesResult = ValuesQueryParser_1.ValuesQueryParser.parseFromLexeme(lexemes, idx); dataQuery = valuesResult.value; idx = valuesResult.newIndex; } else { const selectResult = SelectQueryParser_1.SelectQueryParser.parseFromLexeme(lexemes, idx); dataQuery = selectResult.value; idx = selectResult.newIndex; } let onConflictClause = null; if (((_g = lexemes[idx]) === null || _g === void 0 ? void 0 : _g.value) === "on conflict") { const onConflictResult = this.parseOnConflictClause(lexemes, idx); onConflictClause = onConflictResult.value; idx = onConflictResult.newIndex; } let returningClause = null; if (((_h = lexemes[idx]) === null || _h === void 0 ? void 0 : _h.value) === "returning") { const returningResult = ReturningClauseParser_1.ReturningClauseParser.parseFromLexeme(lexemes, idx); returningClause = returningResult.value; idx = returningResult.newIndex; } if ((onConflictClause === null || onConflictClause === void 0 ? void 0 : onConflictClause.action) === "select" && !returningClause) { throw new Error(`Syntax error: ON CONFLICT DO SELECT requires a RETURNING clause.`); } const insertClause = new Clause_1.InsertClause(targetSource, columnIdentifiers !== null && columnIdentifiers !== void 0 ? columnIdentifiers : null); if (insertKeywordComments.before.length > 0) { insertClause.addPositionedComments("before", insertKeywordComments.before); } if (trailingInsertComments.length > 0) { insertClause.addPositionedComments("after", trailingInsertComments); } if (withClause) { SelectQueryWithClauseHelper_1.SelectQueryWithClauseHelper.setWithClause(dataQuery, withClause); } return { value: new InsertQuery_1.InsertQuery({ insertClause, selectQuery: dataQuery, onConflict: onConflictClause, returning: returningClause }), newIndex: idx }; } static parseOnConflictClause(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) !== "on conflict") { throw new Error(`Syntax error at position ${idx}: Expected 'ON CONFLICT'.`); } idx++; let target = null; let targetKind = null; if (((_b = lexemes[idx]) === null || _b === void 0 ? void 0 : _b.type) === Lexeme_1.TokenType.OpenParen) { const targetStart = idx; idx = this.consumeBalancedParentheses(lexemes, idx); target = (0, ParserStringUtils_1.joinLexemeValues)(lexemes, targetStart, idx); targetKind = "columns"; } else if (((_c = lexemes[idx]) === null || _c === void 0 ? void 0 : _c.value) === "on" && ((_d = lexemes[idx + 1]) === null || _d === void 0 ? void 0 : _d.value) === "constraint") { idx += 2; if (idx >= lexemes.length || lexemes[idx].value === "do select" || lexemes[idx].value === "do update" || lexemes[idx].value === "do nothing") { throw new Error(`Syntax error at position ${idx}: Expected constraint name after ON CONSTRAINT.`); } const targetStart = idx; idx++; target = (0, ParserStringUtils_1.joinLexemeValues)(lexemes, targetStart, idx); targetKind = "constraint"; } const actionToken = (_e = lexemes[idx]) === null || _e === void 0 ? void 0 : _e.value; if (actionToken === "do nothing") { idx++; return { value: new Clause_1.OnConflictClause({ target, targetKind, action: "nothing", }), newIndex: idx }; } if (actionToken === "do update") { idx++; const setResult = SetClauseParser_1.SetClauseParser.parseFromLexeme(lexemes, idx); idx = setResult.newIndex; let whereClause = null; if (((_f = lexemes[idx]) === null || _f === void 0 ? void 0 : _f.value) === "where") { const whereResult = WhereClauseParser_1.WhereClauseParser.parseFromLexeme(lexemes, idx); whereClause = whereResult.value; idx = whereResult.newIndex; } return { value: new Clause_1.OnConflictClause({ target, targetKind, action: "update", setClause: setResult.setClause, whereClause, }), newIndex: idx }; } if (actionToken !== "do select") { const found = (_h = (_g = lexemes[idx]) === null || _g === void 0 ? void 0 : _g.value) !== null && _h !== void 0 ? _h : "end of input"; throw new Error(`Syntax error at position ${idx}: Expected 'DO SELECT', 'DO UPDATE', or 'DO NOTHING' after ON CONFLICT but found '${found}'.`); } idx++; let forClause = null; if (((_j = lexemes[idx]) === null || _j === void 0 ? void 0 : _j.value) === "for") { const forResult = ForClauseParser_1.ForClauseParser.parseFromLexeme(lexemes, idx); forClause = forResult.value; idx = forResult.newIndex; } let whereClause = null; if (((_k = lexemes[idx]) === null || _k === void 0 ? void 0 : _k.value) === "where") { const whereResult = WhereClauseParser_1.WhereClauseParser.parseFromLexeme(lexemes, idx); whereClause = whereResult.value; idx = whereResult.newIndex; } return { value: new Clause_1.OnConflictClause({ target, targetKind, action: "select", forClause, whereClause, }), newIndex: idx }; } static consumeBalancedParentheses(lexemes, index) { let idx = index; let depth = 0; while (idx < lexemes.length) { if (lexemes[idx].type === Lexeme_1.TokenType.OpenParen) { depth++; } else if (lexemes[idx].type === Lexeme_1.TokenType.CloseParen) { depth--; if (depth === 0) { return idx + 1; } } idx++; } throw new Error(`Syntax error at position ${index}: Expected ')' after ON CONFLICT target.`); } } exports.InsertQueryParser = InsertQueryParser; //# sourceMappingURL=InsertQueryParser.js.map