UNPKG

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
"use strict"; 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