rawsql-ts
Version:
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
177 lines • 8.24 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");
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 SELECT query with parentheses
const { selectQuery, trailingComments, newIndex: selectIndex } = this.parseInnerSelectQuery(lexemes, idx);
idx = selectIndex;
// 6. Create CommonTable instance
const value = new Clause_1.CommonTable(selectQuery, 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 parseInnerSelectQuery(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 = SelectQueryParser_1.SelectQueryParser.parseFromLexeme(lexemes, idx);
idx = queryResult.newIndex;
// Add comments from the opening parenthesis as header comments for the inner query
if (cteQueryHeaderComments.length > 0) {
if (queryResult.value.headerComments) {
queryResult.value.headerComments = [...cteQueryHeaderComments, ...queryResult.value.headerComments];
}
else {
queryResult.value.headerComments = 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 {
selectQuery: queryResult.value,
trailingComments: closingParenComments.length > 0 ? closingParenComments : null,
newIndex: idx
};
}
// 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