rawsql-ts
Version:
High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
279 lines • 14 kB
JavaScript
"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