rawsql-ts
Version:
High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
266 lines • 12.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommonTableParser = void 0;
const Clause_1 = require("../models/Clause");
const Lexeme_1 = require("../models/Lexeme");
const SqlTokenizer_1 = require("./SqlTokenizer");
const SelectQueryParser_1 = require("./SelectQueryParser");
const SourceAliasExpressionParser_1 = require("./SourceAliasExpressionParser");
const InsertQueryParser_1 = require("./InsertQueryParser");
const UpdateQueryParser_1 = require("./UpdateQueryParser");
const DeleteQueryParser_1 = require("./DeleteQueryParser");
const MergeQueryParser_1 = require("./MergeQueryParser");
const InsertQuery_1 = require("../models/InsertQuery");
const UpdateQuery_1 = require("../models/UpdateQuery");
const DeleteQuery_1 = require("../models/DeleteQuery");
const MergeQuery_1 = require("../models/MergeQuery");
const SelectQueryWithClauseHelper_1 = require("../utils/SelectQueryWithClauseHelper");
const WithClauseParser_1 = require("./WithClauseParser");
class CommonTableParser {
// Parse SQL string to AST (was: parse)
static parse(query) {
const tokenizer = new SqlTokenizer_1.SqlTokenizer(query); // Initialize tokenizer
const lexemes = tokenizer.readLexmes(); // Get tokens
// Parse
const result = this.parseFromLexeme(lexemes, 0);
// Error if there are remaining tokens
if (result.newIndex < lexemes.length) {
throw new Error(`Syntax error: Unexpected token "${lexemes[result.newIndex].value}" at position ${result.newIndex}. The CommonTable definition is complete but there are additional tokens.`);
}
return result.value;
}
// Parse from lexeme array (was: parse)
static parseFromLexeme(lexemes, index) {
let idx = index;
// 1. Parse alias and optional column aliases
const aliasResult = SourceAliasExpressionParser_1.SourceAliasExpressionParser.parseFromLexeme(lexemes, idx);
idx = aliasResult.newIndex;
// 2. Collect preceding comments for this CTE
this.collectPrecedingComments(lexemes, index, aliasResult);
// 3. Parse AS keyword
idx = this.parseAsKeyword(lexemes, idx);
// 4. Parse optional MATERIALIZED flag
const { materialized, newIndex: materializedIndex } = this.parseMaterializedFlag(lexemes, idx);
idx = materializedIndex;
// 5. Parse inner CTE query with parentheses
const { query, trailingComments, newIndex: queryIndex } = this.parseInnerQuery(lexemes, idx);
idx = queryIndex;
// 6. Create CommonTable instance
const value = new Clause_1.CommonTable(query, aliasResult.value, materialized);
return {
value,
newIndex: idx,
trailingComments
};
}
// Collect comments from preceding tokens that should belong to this CTE
static collectPrecedingComments(lexemes, index, aliasResult) {
var _a;
if (!((_a = aliasResult.value) === null || _a === void 0 ? void 0 : _a.table))
return;
const cteTable = aliasResult.value.table;
for (let i = index - 1; i >= 0; i--) {
const token = lexemes[i];
// Handle WITH keyword specially - collect its "after" comments for the first CTE
if (token.value.toLowerCase() === 'with') {
this.collectWithTokenComments(token, cteTable);
break; // Stop at WITH keyword
}
// Stop looking if we hit a comma (indicates previous CTE) or RECURSIVE keyword
if (token.type & Lexeme_1.TokenType.Comma || token.value.toLowerCase() === 'recursive') {
break;
}
// Collect comments from tokens before the CTE name
this.collectTokenComments(token, cteTable);
}
}
// Collect comments from WITH token
static collectWithTokenComments(token, cteTable) {
let hasPositionedComments = false;
if (token.positionedComments && token.positionedComments.length > 0) {
for (const posComment of token.positionedComments) {
if (posComment.position === 'after' && posComment.comments) {
this.addPositionedComment(cteTable, 'before', posComment.comments);
hasPositionedComments = true;
}
}
}
// Only use legacy comments if no positioned comments were found
if (!hasPositionedComments && token.comments && token.comments.length > 0) {
this.addPositionedComment(cteTable, 'before', token.comments);
}
}
// Collect comments from a token
static collectTokenComments(token, cteTable) {
if (token.comments && token.comments.length > 0) {
this.addPositionedComment(cteTable, 'before', token.comments);
}
if (token.positionedComments && token.positionedComments.length > 0) {
if (!cteTable.positionedComments) {
cteTable.positionedComments = [];
}
cteTable.positionedComments.unshift(...token.positionedComments);
}
}
// Helper to add positioned comment
static addPositionedComment(cteTable, position, comments) {
if (!cteTable.positionedComments) {
cteTable.positionedComments = [];
}
cteTable.positionedComments.unshift({
position,
comments: [...comments]
});
}
// Parse AS keyword
static parseAsKeyword(lexemes, index) {
if (index < lexemes.length && lexemes[index].value !== "as") {
throw new Error(`Syntax error at position ${index}: Expected 'AS' keyword after CTE name but found "${lexemes[index].value}".`);
}
return index + 1; // Skip 'AS' keyword
}
// Parse optional MATERIALIZED flag
static parseMaterializedFlag(lexemes, index) {
if (index >= lexemes.length) {
return { materialized: null, newIndex: index };
}
const currentValue = lexemes[index].value;
if (currentValue === "materialized") {
return { materialized: true, newIndex: index + 1 };
}
else if (currentValue === "not materialized") {
return { materialized: false, newIndex: index + 1 };
}
return { materialized: null, newIndex: index };
}
// Parse inner SELECT query with parentheses
static parseInnerQuery(lexemes, index) {
let idx = index;
if (idx < lexemes.length && lexemes[idx].type !== Lexeme_1.TokenType.OpenParen) {
throw new Error(`Syntax error at position ${idx}: Expected '(' after CTE name but found "${lexemes[idx].value}".`);
}
// Capture comments from the opening parenthesis for the CTE inner query
const cteQueryHeaderComments = this.extractComments(lexemes[idx]);
idx++; // Skip opening parenthesis
const queryResult = this.parseCteQuery(lexemes, idx);
idx = queryResult.newIndex;
// Attach comments from the opening parenthesis to the start of the inner query.
this.applyCteHeaderComments(queryResult.value, cteQueryHeaderComments);
if (idx < lexemes.length && lexemes[idx].type !== Lexeme_1.TokenType.CloseParen) {
throw new Error(`Syntax error at position ${idx}: Expected ')' after CTE query but found "${lexemes[idx].value}".`);
}
// Capture comments from the closing parenthesis
const closingParenComments = this.extractComments(lexemes[idx]);
idx++; // Skip closing parenthesis
return {
query: queryResult.value,
trailingComments: closingParenComments.length > 0 ? closingParenComments : null,
newIndex: idx
};
}
static parseCteQuery(lexemes, index) {
if (index >= lexemes.length) {
throw new Error(`Syntax error at position ${index}: Expected CTE query but found end of input.`);
}
const firstToken = lexemes[index].value.toLowerCase();
switch (firstToken) {
case "select":
case "values":
return SelectQueryParser_1.SelectQueryParser.parseFromLexeme(lexemes, index);
case "insert into":
return InsertQueryParser_1.InsertQueryParser.parseFromLexeme(lexemes, index);
case "update":
return UpdateQueryParser_1.UpdateQueryParser.parseFromLexeme(lexemes, index);
case "delete from":
return DeleteQueryParser_1.DeleteQueryParser.parseFromLexeme(lexemes, index);
case "merge into":
return MergeQueryParser_1.MergeQueryParser.parseFromLexeme(lexemes, index);
case "with": {
// Determine statement type after WITH to route to the correct parser.
const commandAfterWith = this.getCommandAfterWith(lexemes, index);
switch (commandAfterWith) {
case "insert into":
return InsertQueryParser_1.InsertQueryParser.parseFromLexeme(lexemes, index);
case "update":
return UpdateQueryParser_1.UpdateQueryParser.parseFromLexeme(lexemes, index);
case "delete from":
return DeleteQueryParser_1.DeleteQueryParser.parseFromLexeme(lexemes, index);
case "merge into":
return MergeQueryParser_1.MergeQueryParser.parseFromLexeme(lexemes, index);
default:
return SelectQueryParser_1.SelectQueryParser.parseFromLexeme(lexemes, index);
}
}
default:
throw new Error(`Syntax error at position ${index}: Expected SELECT, INSERT, UPDATE, DELETE, MERGE, or WITH in CTE query but found "${lexemes[index].value}".`);
}
}
static applyCteHeaderComments(query, headerComments) {
var _a, _b, _c;
if (headerComments.length === 0) {
return;
}
if (this.isSelectQuery(query)) {
if (query.headerComments) {
query.headerComments = [...headerComments, ...query.headerComments];
}
else {
query.headerComments = headerComments;
}
return;
}
if (query instanceof InsertQuery_1.InsertQuery) {
const withClause = SelectQueryWithClauseHelper_1.SelectQueryWithClauseHelper.getWithClause(query.selectQuery);
const target = withClause !== null && withClause !== void 0 ? withClause : query.insertClause;
target.addPositionedComments("before", headerComments);
return;
}
if (query instanceof UpdateQuery_1.UpdateQuery) {
const target = (_a = query.withClause) !== null && _a !== void 0 ? _a : query.updateClause;
target.addPositionedComments("before", headerComments);
return;
}
if (query instanceof DeleteQuery_1.DeleteQuery) {
const target = (_b = query.withClause) !== null && _b !== void 0 ? _b : query.deleteClause;
target.addPositionedComments("before", headerComments);
return;
}
if (query instanceof MergeQuery_1.MergeQuery) {
const target = (_c = query.withClause) !== null && _c !== void 0 ? _c : query;
target.addPositionedComments("before", headerComments);
}
}
static isSelectQuery(query) {
return "__selectQueryType" in query && query.__selectQueryType === "SelectQuery";
}
static getCommandAfterWith(lexemes, index) {
var _a;
try {
const withResult = WithClauseParser_1.WithClauseParser.parseFromLexeme(lexemes, index);
const next = lexemes[withResult.newIndex];
return (_a = next === null || next === void 0 ? void 0 : next.value.toLowerCase()) !== null && _a !== void 0 ? _a : null;
}
catch {
return null;
}
}
// Extract comments from a lexeme (both positioned and legacy)
static extractComments(lexeme) {
const comments = [];
// Check positioned comments
if (lexeme.positionedComments) {
for (const posComment of lexeme.positionedComments) {
if (posComment.comments) {
comments.push(...posComment.comments);
}
}
}
// Check legacy comments for backward compatibility
if (lexeme.comments && lexeme.comments.length > 0) {
comments.push(...lexeme.comments);
}
return comments;
}
}
exports.CommonTableParser = CommonTableParser;
//# sourceMappingURL=CommonTableParser.js.map