UNPKG

rawsql-ts

Version:

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

354 lines 14.1 kB
import { FetchClause, ForClause, FromClause, FunctionSource, GroupByClause, HavingClause, JoinClause, JoinOnClause, JoinUsingClause, LimitClause, OffsetClause, OrderByClause, OrderByItem, ParenSource, SelectClause, SelectItem, SourceExpression, SubQuerySource, TableSource, WhereClause, WindowFrameClause } from "../models/Clause"; import { BinarySelectQuery, SimpleSelectQuery, ValuesQuery } from "../models/SelectQuery"; import { ArrayExpression, ArrayQueryExpression, BetweenExpression, BinaryExpression, CaseExpression, CaseKeyValuePair, CastExpression, FunctionCall, InlineQuery, ParenExpression, SwitchCaseArgument, TupleExpression, UnaryExpression, ValueList, RawString, StringSpecifierExpression } from "../models/ValueComponent"; /** * A specialized table source collector designed for CTE dependency analysis. * * Unlike the general-purpose TableSourceCollector, this collector: * - Always includes CTE references in results (treats CTEs as valid table sources) * - Always performs deep traversal of subqueries, WHERE clauses, etc. * - Is optimized for dependency analysis rather than database schema analysis * * This collector is specifically designed for use by CTEDependencyAnalyzer to track * which tables/CTEs are referenced by queries at any nesting level. */ export class CTETableReferenceCollector { constructor() { this.tableSources = []; this.visitedNodes = new Set(); this.tableNameMap = new Map(); this.isRootVisit = true; this.handlers = new Map(); // Setup handlers for query components this.handlers.set(SimpleSelectQuery.kind, (expr) => this.visitSimpleSelectQuery(expr)); this.handlers.set(BinarySelectQuery.kind, (expr) => this.visitBinarySelectQuery(expr)); this.handlers.set(ValuesQuery.kind, (expr) => this.visitValuesQuery(expr)); // Note: We intentionally do NOT handle WITH clause and CommonTable // These are processed separately by CTEDependencyAnalyzer for CTE-to-CTE dependencies // Handlers for FROM and JOIN components this.handlers.set(FromClause.kind, (expr) => this.visitFromClause(expr)); this.handlers.set(JoinClause.kind, (expr) => this.visitJoinClause(expr)); this.handlers.set(JoinOnClause.kind, (expr) => this.visitJoinOnClause(expr)); this.handlers.set(JoinUsingClause.kind, (expr) => this.visitJoinUsingClause(expr)); // Source components this.handlers.set(SourceExpression.kind, (expr) => this.visitSourceExpression(expr)); this.handlers.set(TableSource.kind, (expr) => this.visitTableSource(expr)); this.handlers.set(FunctionSource.kind, (expr) => this.visitFunctionSource(expr)); this.handlers.set(ParenSource.kind, (expr) => this.visitParenSource(expr)); this.handlers.set(SubQuerySource.kind, (expr) => this.visitSubQuerySource(expr)); this.handlers.set(InlineQuery.kind, (expr) => this.visitInlineQuery(expr)); // Additional clause handlers for full scanning this.handlers.set(WhereClause.kind, (expr) => this.visitWhereClause(expr)); this.handlers.set(GroupByClause.kind, (expr) => this.visitGroupByClause(expr)); this.handlers.set(HavingClause.kind, (expr) => this.visitHavingClause(expr)); this.handlers.set(OrderByClause.kind, (expr) => this.visitOrderByClause(expr)); this.handlers.set(WindowFrameClause.kind, (expr) => this.visitWindowFrameClause(expr)); this.handlers.set(LimitClause.kind, (expr) => this.visitLimitClause(expr)); this.handlers.set(OffsetClause.kind, (expr) => this.visitOffsetClause(expr)); this.handlers.set(FetchClause.kind, (expr) => this.visitFetchClause(expr)); this.handlers.set(ForClause.kind, (expr) => this.visitForClause(expr)); this.handlers.set(OrderByItem.kind, (expr) => this.visitOrderByItem(expr)); this.handlers.set(SelectClause.kind, (expr) => this.visitSelectClause(expr)); this.handlers.set(SelectItem.kind, (expr) => this.visitSelectItem(expr)); // Value components that might contain table references this.handlers.set(ParenExpression.kind, (expr) => this.visitParenExpression(expr)); this.handlers.set(BinaryExpression.kind, (expr) => this.visitBinaryExpression(expr)); this.handlers.set(UnaryExpression.kind, (expr) => this.visitUnaryExpression(expr)); this.handlers.set(CaseExpression.kind, (expr) => this.visitCaseExpression(expr)); this.handlers.set(CaseKeyValuePair.kind, (expr) => this.visitCaseKeyValuePair(expr)); this.handlers.set(SwitchCaseArgument.kind, (expr) => this.visitSwitchCaseArgument(expr)); this.handlers.set(BetweenExpression.kind, (expr) => this.visitBetweenExpression(expr)); this.handlers.set(FunctionCall.kind, (expr) => this.visitFunctionCall(expr)); this.handlers.set(ArrayExpression.kind, (expr) => this.visitArrayExpression(expr)); this.handlers.set(ArrayQueryExpression.kind, (expr) => this.visitArrayQueryExpression(expr)); this.handlers.set(TupleExpression.kind, (expr) => this.visitTupleExpression(expr)); this.handlers.set(CastExpression.kind, (expr) => this.visitCastExpression(expr)); this.handlers.set(ValueList.kind, (expr) => this.visitValueList(expr)); this.handlers.set(StringSpecifierExpression.kind, (expr) => this.visitStringSpecifierExpression(expr)); } /** * Collects all table references from the given SQL component * @param query The SQL component to analyze * @returns Array of TableSource objects representing all table references */ collect(query) { this.visit(query); return this.getTableSources(); } /** * Gets all collected table sources */ getTableSources() { return this.tableSources; } /** * Reset the collection of table sources */ reset() { this.tableSources = []; this.tableNameMap.clear(); this.visitedNodes.clear(); } /** * Gets a unique identifier for a table source */ getTableIdentifier(source) { // Use QualifiedName for identifier (dot-joined string) if (source.qualifiedName.namespaces && source.qualifiedName.namespaces.length > 0) { return source.qualifiedName.namespaces.map(ns => ns.name).join('.') + '.' + (source.qualifiedName.name instanceof RawString ? source.qualifiedName.name.value : source.qualifiedName.name.name); } else { return source.qualifiedName.name instanceof RawString ? source.qualifiedName.name.value : source.qualifiedName.name.name; } } /** * Main entry point for the visitor pattern. */ 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. */ visitNode(arg) { // 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; } // If no handler found, that's ok - we only care about specific components } visitSimpleSelectQuery(query) { // Skip WITH clause processing - we only want to collect table references from the main query parts // The WITH clause is handled separately by CTEDependencyAnalyzer for CTE-to-CTE dependencies if (query.fromClause) { query.fromClause.accept(this); } if (query.whereClause) { query.whereClause.accept(this); } 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.offsetClause) { query.offsetClause.accept(this); } if (query.fetchClause) { query.fetchClause.accept(this); } if (query.forClause) { query.forClause.accept(this); } query.selectClause.accept(this); } visitBinarySelectQuery(query) { // For UNION-like queries, visit both sides 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); } } visitFromClause(fromClause) { // Check the main source in FROM clause fromClause.source.accept(this); // Check all JOIN clauses if (fromClause.joins) { for (const join of fromClause.joins) { join.accept(this); } } } visitSourceExpression(source) { // Process the actual data source, ignoring aliases source.datasource.accept(this); } visitTableSource(source) { // Get the table identifier for uniqueness check const identifier = this.getTableIdentifier(source); // Include all table sources (both real tables and CTEs) if (!this.tableNameMap.has(identifier)) { this.tableNameMap.set(identifier, true); this.tableSources.push(source); } } visitFunctionSource(source) { // Function sources are not regular table sources, but may contain subqueries in their arguments if (source.argument) { this.visitValueComponent(source.argument); } } visitValueComponent(value) { value.accept(this); } visitParenSource(source) { source.source.accept(this); } visitSubQuerySource(subQuery) { // Always check subqueries in CTE analysis mode subQuery.query.accept(this); } visitInlineQuery(inlineQuery) { // Always visit inline queries inlineQuery.selectQuery.accept(this); } visitJoinClause(joinClause) { // Visit the source being joined joinClause.source.accept(this); // Visit the join condition if (joinClause.condition) { joinClause.condition.accept(this); } } visitJoinOnClause(joinOn) { joinOn.condition.accept(this); } visitJoinUsingClause(joinUsing) { joinUsing.condition.accept(this); } // Additional visitor methods for comprehensive analysis 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); } visitOffsetClause(clause) { clause.value.accept(this); } visitFetchClause(clause) { clause.expression.accept(this); } visitForClause(_clause) { // FOR clause doesn't contain table sources } visitOrderByItem(item) { item.value.accept(this); } visitSelectClause(clause) { for (const item of clause.items) { item.accept(this); } } visitSelectItem(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) { for (const caseItem of switchCase.cases) { caseItem.accept(this); } if (switchCase.elseValue) { switchCase.elseValue.accept(this); } } visitCaseKeyValuePair(pair) { pair.key.accept(this); 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); } if (func.over) { func.over.accept(this); } } visitArrayExpression(expr) { expr.expression.accept(this); } visitArrayQueryExpression(expr) { expr.query.accept(this); } visitTupleExpression(expr) { for (const value of expr.values) { value.accept(this); } } visitCastExpression(expr) { expr.input.accept(this); expr.castType.accept(this); } visitValueList(valueList) { for (const value of valueList.values) { value.accept(this); } } visitStringSpecifierExpression(_expr) { // StringSpecifierExpression doesn't contain table references } } //# sourceMappingURL=CTETableReferenceCollector.js.map