UNPKG

rawsql-ts

Version:

[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.

393 lines 15.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CTECollector = void 0; const Clause_1 = require("../models/Clause"); const SelectQuery_1 = require("../models/SelectQuery"); const ValueComponent_1 = require("../models/ValueComponent"); /** * A visitor that collects all CommonTable instances from a SQL query structure. * This includes tables from: * - WITH clauses * - Subqueries * - Inline queries * - UNION queries * - Value components that may contain queries */ class CTECollector { constructor() { this.commonTables = []; this.visitedNodes = new Set(); this.isRootVisit = true; this.handlers = new Map(); // Setup handlers for all component types that might contain CommonTables // SelectQuery types this.handlers.set(SelectQuery_1.SimpleSelectQuery.kind, (expr) => this.visitSimpleSelectQuery(expr)); this.handlers.set(SelectQuery_1.BinarySelectQuery.kind, (expr) => this.visitBinarySelectQuery(expr)); this.handlers.set(SelectQuery_1.ValuesQuery.kind, (expr) => this.visitValuesQuery(expr)); // WITH clause that directly contains CommonTables this.handlers.set(Clause_1.WithClause.kind, (expr) => this.visitWithClause(expr)); this.handlers.set(Clause_1.CommonTable.kind, (expr) => this.visitCommonTable(expr)); // SelectComponent types this.handlers.set(Clause_1.SelectItem.kind, (expr) => this.visitSelectItem(expr)); // Identifiers and raw strings (leaf nodes that don't need traversal) this.handlers.set(ValueComponent_1.IdentifierString.kind, (expr) => this.visitIdentifierString(expr)); this.handlers.set(ValueComponent_1.RawString.kind, (expr) => this.visitRawString(expr)); this.handlers.set(ValueComponent_1.ColumnReference.kind, (expr) => this.visitColumnReference(expr)); this.handlers.set(ValueComponent_1.ParameterExpression.kind, (expr) => this.visitParameterExpression(expr)); this.handlers.set(ValueComponent_1.LiteralValue.kind, (expr) => this.visitLiteralValue(expr)); // Source components this.handlers.set(Clause_1.SourceExpression.kind, (expr) => this.visitSourceExpression(expr)); this.handlers.set(Clause_1.TableSource.kind, (expr) => this.visitTableSource(expr)); this.handlers.set(Clause_1.ParenSource.kind, (expr) => this.visitParenSource(expr)); // Subqueries and inline queries this.handlers.set(Clause_1.SubQuerySource.kind, (expr) => this.visitSubQuerySource(expr)); this.handlers.set(ValueComponent_1.InlineQuery.kind, (expr) => this.visitInlineQuery(expr)); // FROM and JOIN clauses this.handlers.set(Clause_1.FromClause.kind, (expr) => this.visitFromClause(expr)); this.handlers.set(Clause_1.JoinClause.kind, (expr) => this.visitJoinClause(expr)); this.handlers.set(Clause_1.JoinOnClause.kind, (expr) => this.visitJoinOnClause(expr)); this.handlers.set(Clause_1.JoinUsingClause.kind, (expr) => this.visitJoinUsingClause(expr)); // WHERE clause this.handlers.set(Clause_1.WhereClause.kind, (expr) => this.visitWhereClause(expr)); // Value components that might contain subqueries this.handlers.set(ValueComponent_1.ParenExpression.kind, (expr) => this.visitParenExpression(expr)); this.handlers.set(ValueComponent_1.BinaryExpression.kind, (expr) => this.visitBinaryExpression(expr)); this.handlers.set(ValueComponent_1.UnaryExpression.kind, (expr) => this.visitUnaryExpression(expr)); this.handlers.set(ValueComponent_1.CaseExpression.kind, (expr) => this.visitCaseExpression(expr)); this.handlers.set(ValueComponent_1.CaseKeyValuePair.kind, (expr) => this.visitCaseKeyValuePair(expr)); this.handlers.set(ValueComponent_1.SwitchCaseArgument.kind, (expr) => this.visitSwitchCaseArgument(expr)); this.handlers.set(ValueComponent_1.BetweenExpression.kind, (expr) => this.visitBetweenExpression(expr)); this.handlers.set(ValueComponent_1.FunctionCall.kind, (expr) => this.visitFunctionCall(expr)); this.handlers.set(ValueComponent_1.ArrayExpression.kind, (expr) => this.visitArrayExpression(expr)); this.handlers.set(ValueComponent_1.TupleExpression.kind, (expr) => this.visitTupleExpression(expr)); this.handlers.set(ValueComponent_1.CastExpression.kind, (expr) => this.visitCastExpression(expr)); this.handlers.set(ValueComponent_1.WindowFrameExpression.kind, (expr) => this.visitWindowFrameExpression(expr)); this.handlers.set(ValueComponent_1.WindowFrameSpec.kind, (expr) => this.visitWindowFrameSpec(expr)); this.handlers.set(ValueComponent_1.TypeValue.kind, (expr) => this.visitTypeValue(expr)); this.handlers.set(ValueComponent_1.ValueList.kind, (expr) => this.visitValueList(expr)); // Add handlers for other clause types this.handlers.set(Clause_1.SelectClause.kind, (expr) => this.visitSelectClause(expr)); this.handlers.set(Clause_1.GroupByClause.kind, (expr) => this.visitGroupByClause(expr)); this.handlers.set(Clause_1.HavingClause.kind, (expr) => this.visitHavingClause(expr)); this.handlers.set(Clause_1.OrderByClause.kind, (expr) => this.visitOrderByClause(expr)); this.handlers.set(Clause_1.WindowFrameClause.kind, (expr) => this.visitWindowFrameClause(expr)); this.handlers.set(Clause_1.LimitClause.kind, (expr) => this.visitLimitClause(expr)); this.handlers.set(Clause_1.ForClause.kind, (expr) => this.visitForClause(expr)); this.handlers.set(Clause_1.OrderByItem.kind, (expr) => this.visitOrderByItem(expr)); this.handlers.set(Clause_1.PartitionByClause.kind, (expr) => this.visitPartitionByClause(expr)); } /** * Get all collected CommonTables */ getCommonTables() { return this.commonTables; } /** * Reset the collection of CommonTables */ reset() { this.commonTables = []; this.visitedNodes.clear(); } collect(query) { // Visit the query to collect all CommonTables this.visit(query); return this.getCommonTables(); } /** * Main entry point for the visitor pattern. * Implements the shallow visit pattern to distinguish between root and recursive visits. */ visit(arg) { // If not a root visit, just visit the node and return if (!this.isRootVisit) { this.visitNode(arg); return; } // If this is a root visit, we need to reset the state this.reset(); this.isRootVisit = false; try { this.visitNode(arg); } finally { // Regardless of success or failure, reset the root visit flag this.isRootVisit = true; } } /** * Internal visit method used for all nodes. * This separates the visit flag management from the actual node visitation logic. */ visitNode(arg) { var _a, _b; // Skip if we've already visited this node to prevent infinite recursion if (this.visitedNodes.has(arg)) { return; } // Mark as visited this.visitedNodes.add(arg); const handler = this.handlers.get(arg.getKind()); if (handler) { handler(arg); return; } // Provide more detailed error message const kindSymbol = ((_a = arg.getKind()) === null || _a === void 0 ? void 0 : _a.toString()) || 'unknown'; const constructor = ((_b = arg.constructor) === null || _b === void 0 ? void 0 : _b.name) || 'unknown'; throw new Error(`[CTECollector] No handler for ${constructor} with kind ${kindSymbol}.`); } visitSimpleSelectQuery(query) { // The order matters here! // First, visit all clauses that might contain nested CTEs // to ensure inner CTEs are collected before outer CTEs // Check FROM clause first (can contain subqueries with nested CTEs) if (query.fromClause) { query.fromClause.accept(this); } // Check WHERE clause (can contain subqueries with WITH clauses) if (query.whereClause) { query.whereClause.accept(this); } // Check other clauses that might contain CTEs if (query.groupByClause) { query.groupByClause.accept(this); } if (query.havingClause) { query.havingClause.accept(this); } if (query.orderByClause) { query.orderByClause.accept(this); } if (query.windowClause) { for (const win of query.windowClause.windows) { win.accept(this); } } if (query.limitClause) { query.limitClause.accept(this); } if (query.forClause) { query.forClause.accept(this); } // Check SELECT clause query.selectClause.accept(this); // Finally check the WITH clause after all nested CTEs have been collected // This ensures inner CTEs are collected before outer CTEs if (query.withClause) { query.withClause.accept(this); } } visitBinarySelectQuery(query) { // Visit both sides of the binary query (UNION, EXCEPT, etc.) query.left.accept(this); query.right.accept(this); } visitValuesQuery(query) { // VALUES queries might contain subqueries in tuple expressions for (const tuple of query.tuples) { tuple.accept(this); } } visitWithClause(withClause) { // Visit each CommonTable // Simply process tables in sequence // Note: visitCommonTable already handles nested CTEs for (let i = 0; i < withClause.tables.length; i++) { const commonTable = withClause.tables[i]; commonTable.accept(this); } } visitCommonTable(commonTable) { // Process CommonTable directly within the query // Use the same instance to process the query instead of creating another Collector commonTable.query.accept(this); // Add current CTE after all nested CTEs have been added this.commonTables.push(commonTable); } visitSelectClause(clause) { // Check each item in the select clause for (const item of clause.items) { item.accept(this); } } visitSelectItem(item) { // Select items might contain subqueries item.value.accept(this); } visitFromClause(fromClause) { // Check the source fromClause.source.accept(this); // Check joins if (fromClause.joins) { for (const join of fromClause.joins) { join.accept(this); } } } visitSourceExpression(source) { source.datasource.accept(this); // The alias part doesn't contain subqueries so we skip it } visitTableSource(source) { // Table sources don't contain subqueries, nothing to do } visitParenSource(source) { source.source.accept(this); } visitSubQuerySource(subQuery) { subQuery.query.accept(this); } visitInlineQuery(inlineQuery) { inlineQuery.selectQuery.accept(this); } visitJoinClause(joinClause) { // Check join source joinClause.source.accept(this); // Check join condition if (joinClause.condition) { joinClause.condition.accept(this); } } visitJoinOnClause(joinOn) { joinOn.condition.accept(this); } visitJoinUsingClause(joinUsing) { joinUsing.condition.accept(this); } visitWhereClause(whereClause) { whereClause.condition.accept(this); } visitGroupByClause(clause) { for (const item of clause.grouping) { item.accept(this); } } visitHavingClause(clause) { clause.condition.accept(this); } visitOrderByClause(clause) { for (const item of clause.order) { item.accept(this); } } visitWindowFrameClause(clause) { clause.expression.accept(this); } visitLimitClause(clause) { clause.value.accept(this); } visitForClause(clause) { // FOR clause doesn't contain subqueries } visitOrderByItem(item) { item.value.accept(this); } visitParenExpression(expr) { expr.expression.accept(this); } visitBinaryExpression(expr) { expr.left.accept(this); expr.right.accept(this); } visitUnaryExpression(expr) { expr.expression.accept(this); } visitCaseExpression(expr) { if (expr.condition) { expr.condition.accept(this); } expr.switchCase.accept(this); } visitSwitchCaseArgument(switchCase) { // Check all case expressions for (const caseItem of switchCase.cases) { caseItem.accept(this); } // Check ELSE expression if (switchCase.elseValue) { switchCase.elseValue.accept(this); } } visitCaseKeyValuePair(pair) { // Check the WHEN condition pair.key.accept(this); // Check the THEN value pair.value.accept(this); } visitBetweenExpression(expr) { expr.expression.accept(this); expr.lower.accept(this); expr.upper.accept(this); } visitFunctionCall(func) { if (func.argument) { func.argument.accept(this); } // Check OVER clause if present if (func.over) { func.over.accept(this); } } visitArrayExpression(expr) { expr.expression.accept(this); } visitTupleExpression(expr) { // Check each value in the tuple for possible subqueries for (const value of expr.values) { value.accept(this); } } visitCastExpression(expr) { // Check the input expression expr.input.accept(this); // Check the type expression expr.castType.accept(this); } visitTypeValue(expr) { // Visit the argument if present if (expr.argument) { expr.argument.accept(this); } // The type itself doesn't contain subqueries } visitWindowFrameExpression(expr) { if (expr.partition) { expr.partition.accept(this); } if (expr.order) { expr.order.accept(this); } if (expr.frameSpec) { expr.frameSpec.accept(this); } } visitWindowFrameSpec(spec) { // WindowFrameSpec is a simple value object, nothing to traverse } visitIdentifierString(ident) { // Leaf node, nothing to traverse } visitRawString(raw) { // Leaf node, nothing to traverse } visitColumnReference(column) { // Column references don't have subqueries } visitParameterExpression(param) { // Parameter expressions don't have child components } visitLiteralValue(value) { // Literal values are leaf nodes } visitPartitionByClause(partitionBy) { // don't have subqueries } visitValueList(valueList) { for (const value of valueList.values) { value.accept(this); } } } exports.CTECollector = CTECollector; //# sourceMappingURL=CTECollector.js.map