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
JavaScript
"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