rawsql-ts
Version: 
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
165 lines • 7.15 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WithClauseParser = void 0;
const Clause_1 = require("../models/Clause");
const Lexeme_1 = require("../models/Lexeme");
const SqlTokenizer_1 = require("./SqlTokenizer");
const CommonTableParser_1 = require("./CommonTableParser");
/**
 * Parser for SQL WITH clauses (Common Table Expressions - CTEs).
 * Parses only the WITH clause portion of SQL, not the entire query.
 *
 * **Note**: For most use cases, use `SelectQueryParser` which provides more comprehensive SQL parsing.
 * This parser should only be used for the special case where you need to analyze only the WITH clause portion.
 *
 * @example
 * ```typescript
 * // Parses only the WITH clause, not the following SELECT
 * const sql = "WITH recursive_cte AS (SELECT 1 as n UNION SELECT n+1 FROM recursive_cte WHERE n < 10)";
 * const withClause = WithClauseParser.parse(sql);
 * console.log(withClause.recursive); // true
 * console.log(withClause.tables.length); // 1
 * ```
 */
class WithClauseParser {
    /**
     * Parses a SQL string containing only a WITH clause into a WithClause AST.
     * The input should contain only the WITH clause, not the subsequent main query.
     *
     * @param query - The SQL string containing only the WITH clause
     * @returns The parsed WithClause object
     * @throws Error if the syntax is invalid or there are unexpected tokens after the WITH clause
     *
     * @example
     * ```typescript
     * // Correct: Only the WITH clause
     * const sql = "WITH users_data AS (SELECT id, name FROM users)";
     * const withClause = WithClauseParser.parse(sql);
     *
     * // Error: Contains SELECT after WITH clause
     * // const badSql = "WITH users_data AS (SELECT id, name FROM users) SELECT * FROM users_data";
     * ```
     */
    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 WITH clause is complete but there are additional tokens.`);
        }
        return result.value;
    }
    /**
     * Parses a WITH clause from an array of lexemes starting at the specified index.
     *
     * @param lexemes - Array of lexemes to parse from
     * @param index - Starting index in the lexemes array
     * @returns Object containing the parsed WithClause and the new index position
     * @throws Error if the syntax is invalid or WITH keyword is not found
     *
     * @example
     * ```typescript
     * const tokenizer = new SqlTokenizer("WITH cte AS (SELECT 1)");
     * const lexemes = tokenizer.readLexmes();
     * const result = WithClauseParser.parseFromLexeme(lexemes, 0);
     * console.log(result.value.tables.length); // 1
     * console.log(result.newIndex); // position after the WITH clause
     * ```
     */
    static parseFromLexeme(lexemes, index) {
        let idx = index;
        // Extract header comments from WITH token
        const headerComments = this.extractWithTokenHeaderComments(lexemes, idx);
        // Parse WITH keyword
        idx = this.parseWithKeyword(lexemes, idx);
        // Parse optional RECURSIVE keyword
        const { recursive, newIndex: recursiveIndex } = this.parseRecursiveFlag(lexemes, idx);
        idx = recursiveIndex;
        // Parse all CTEs
        const { tables, trailingComments, newIndex: ctesIndex } = this.parseAllCommonTables(lexemes, idx);
        idx = ctesIndex;
        // Create WITH clause and apply trailing comments directly
        const withClause = new Clause_1.WithClause(recursive, tables);
        if (trailingComments.length > 0) {
            withClause.trailingComments = trailingComments;
        }
        return {
            value: withClause,
            newIndex: idx,
            headerComments: headerComments
        };
    }
    // Extract header comments from WITH token
    static extractWithTokenHeaderComments(lexemes, index) {
        if (index >= lexemes.length)
            return null;
        const withToken = lexemes[index];
        let headerComments = null;
        // Extract positioned comments: only "before" comments are header comments
        if (withToken.positionedComments && withToken.positionedComments.length > 0) {
            for (const posComment of withToken.positionedComments) {
                if (posComment.position === 'before' && posComment.comments) {
                    if (!headerComments)
                        headerComments = [];
                    headerComments.push(...posComment.comments);
                }
            }
        }
        // Fallback to legacy comments if no positioned comments (for backward compatibility)
        if (!headerComments && withToken.comments && withToken.comments.length > 0) {
            headerComments = [...withToken.comments];
        }
        return headerComments;
    }
    // Parse WITH keyword
    static parseWithKeyword(lexemes, index) {
        if (index < lexemes.length && lexemes[index].value.toLowerCase() === "with") {
            return index + 1;
        }
        else {
            throw new Error(`Syntax error at position ${index}: Expected WITH keyword.`);
        }
    }
    // Parse optional RECURSIVE keyword
    static parseRecursiveFlag(lexemes, index) {
        const recursive = index < lexemes.length && lexemes[index].value.toLowerCase() === "recursive";
        return {
            recursive,
            newIndex: recursive ? index + 1 : index
        };
    }
    // Parse all common table expressions
    static parseAllCommonTables(lexemes, index) {
        let idx = index;
        const tables = [];
        const allTrailingComments = [];
        // Parse first CTE (required)
        const firstCte = CommonTableParser_1.CommonTableParser.parseFromLexeme(lexemes, idx);
        tables.push(firstCte.value);
        idx = firstCte.newIndex;
        // Collect trailing comments from first CTE directly
        if (firstCte.trailingComments) {
            allTrailingComments.push(...firstCte.trailingComments);
        }
        // Parse additional CTEs (optional)
        while (idx < lexemes.length && (lexemes[idx].type & Lexeme_1.TokenType.Comma)) {
            idx++; // Skip comma
            const cteResult = CommonTableParser_1.CommonTableParser.parseFromLexeme(lexemes, idx);
            tables.push(cteResult.value);
            idx = cteResult.newIndex;
            // Collect trailing comments from this CTE directly
            if (cteResult.trailingComments) {
                allTrailingComments.push(...cteResult.trailingComments);
            }
        }
        return {
            tables,
            trailingComments: allTrailingComments,
            newIndex: idx
        };
    }
}
exports.WithClauseParser = WithClauseParser;
//# sourceMappingURL=WithClauseParser.js.map