UNPKG

rawsql-ts

Version:

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

323 lines 15.5 kB
import { ForClause, FromClause, GroupByClause, HavingClause, JoinClause, JoinOnClause, JoinUsingClause, LimitClause, OrderByClause, OrderByItem, ParenSource, SelectClause, SelectItem, SourceExpression, SubQuerySource, TableSource, WhereClause, WindowFrameClause, WindowsClause } from "../models/Clause"; import { BinarySelectQuery, SimpleSelectQuery, ValuesQuery } from "../models/SelectQuery"; import { ArrayExpression, BetweenExpression, BinaryExpression, CaseExpression, CaseKeyValuePair, CastExpression, ColumnReference, FunctionCall, InlineQuery, ParenExpression, ParameterExpression, SwitchCaseArgument, TupleExpression, UnaryExpression, WindowFrameExpression, IdentifierString, RawString, WindowFrameSpec, LiteralValue, TypeValue } from "../models/ValueComponent"; /** * A visitor that disables all WITH clauses in a SQL query structure. * This processes and removes WITH clauses from: * - Simple SELECT queries * - Binary queries (UNION, EXCEPT, etc.) * - Subqueries * - Inline queries * * It maintains the CTE queries themselves but restructures the query to not use * the WITH clause syntactical construct. */ export class CTEDisabler { constructor() { this.visitedNodes = new Set(); this.isRootVisit = true; this.handlers = new Map(); // Setup handlers for all component types that might contain WITH clauses // SelectQuery types 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)); // SelectComponent types this.handlers.set(SelectItem.kind, (expr) => this.visitSelectItem(expr)); // Identifiers and raw strings this.handlers.set(IdentifierString.kind, (expr) => this.visitIdentifierString(expr)); this.handlers.set(RawString.kind, (expr) => this.visitRawString(expr)); this.handlers.set(ColumnReference.kind, (expr) => this.visitColumnReference(expr)); this.handlers.set(ParameterExpression.kind, (expr) => this.visitParameterExpression(expr)); this.handlers.set(LiteralValue.kind, (expr) => this.visitLiteralValue(expr)); // Source components this.handlers.set(SourceExpression.kind, (expr) => this.visitSourceExpression(expr)); this.handlers.set(TableSource.kind, (expr) => this.visitTableSource(expr)); this.handlers.set(ParenSource.kind, (expr) => this.visitParenSource(expr)); // Subqueries and inline queries this.handlers.set(SubQuerySource.kind, (expr) => this.visitSubQuerySource(expr)); this.handlers.set(InlineQuery.kind, (expr) => this.visitInlineQuery(expr)); // FROM and JOIN clauses 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)); // WHERE clause this.handlers.set(WhereClause.kind, (expr) => this.visitWhereClause(expr)); // Value components that might contain subqueries 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(TupleExpression.kind, (expr) => this.visitTupleExpression(expr)); this.handlers.set(CastExpression.kind, (expr) => this.visitCastExpression(expr)); this.handlers.set(WindowFrameExpression.kind, (expr) => this.visitWindowFrameExpression(expr)); this.handlers.set(WindowFrameSpec.kind, (expr) => this.visitWindowFrameSpec(expr)); this.handlers.set(TypeValue.kind, (expr) => this.visitTypeValue(expr)); // Add handlers for other clause types this.handlers.set(SelectClause.kind, (expr) => this.visitSelectClause(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(ForClause.kind, (expr) => this.visitForClause(expr)); this.handlers.set(OrderByItem.kind, (expr) => this.visitOrderByItem(expr)); } /** * Reset the visited nodes tracking */ reset() { this.visitedNodes.clear(); } execute(arg) { // Reset the visited nodes before starting the visit this.reset(); return this.visit(arg); } /** * 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) { return this.visitNode(arg); } // If this is a root visit, we need to reset the state this.reset(); this.isRootVisit = false; try { return 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; // Check for circular references - if node already visited, return as is if (this.visitedNodes.has(arg)) { return arg; } // Mark as visited node this.visitedNodes.add(arg); const handler = this.handlers.get(arg.getKind()); if (handler) { return handler(arg); } // 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(`[CTEDisabler] No handler for ${constructor} with kind ${kindSymbol}.`); } visitSimpleSelectQuery(arg) { if (arg.withClause) { arg.withClause.tables.forEach(table => { this.visit(table.query); }); } arg.withClause = null; // Explicitly remove WITH clause // Visit the components of the SimpleSelectQuery arg.selectClause = this.visit(arg.selectClause); arg.fromClause = arg.fromClause ? this.visit(arg.fromClause) : null; arg.whereClause = arg.whereClause ? this.visit(arg.whereClause) : null; arg.groupByClause = arg.groupByClause ? this.visit(arg.groupByClause) : null; arg.havingClause = arg.havingClause ? this.visit(arg.havingClause) : null; arg.orderByClause = arg.orderByClause ? this.visit(arg.orderByClause) : null; if (arg.windowClause) { arg.windowClause = new WindowsClause(arg.windowClause.windows.map(w => this.visit(w))); } arg.limitClause = arg.limitClause ? this.visit(arg.limitClause) : null; arg.forClause = arg.forClause ? this.visit(arg.forClause) : null; return arg; } visitBinarySelectQuery(query) { query.left = this.visit(query.left); query.right = this.visit(query.right); return query; } visitValuesQuery(query) { const newTuples = query.tuples.map(tuple => this.visit(tuple)); return new ValuesQuery(newTuples); } visitSelectClause(clause) { const newItems = clause.items.map(item => { return this.visit(item); }); return new SelectClause(newItems, clause.distinct); } visitFromClause(clause) { const newSource = this.visit(clause.source); const newJoins = clause.joins ? clause.joins.map(join => this.visit(join)) : null; return new FromClause(newSource, newJoins); } visitSubQuerySource(subQuery) { const newQuery = this.visit(subQuery.query); return new SubQuerySource(newQuery); } visitInlineQuery(inlineQuery) { const newQuery = this.visit(inlineQuery.selectQuery); return new InlineQuery(newQuery); } visitJoinClause(joinClause) { const newSource = this.visit(joinClause.source); const newCondition = joinClause.condition ? this.visit(joinClause.condition) : null; return new JoinClause(joinClause.joinType.value, newSource, newCondition, joinClause.lateral); } visitJoinOnClause(joinOn) { const newCondition = this.visit(joinOn.condition); return new JoinOnClause(newCondition); } visitJoinUsingClause(joinUsing) { const newCondition = this.visit(joinUsing.condition); return new JoinUsingClause(newCondition); } visitWhereClause(whereClause) { const newCondition = this.visit(whereClause.condition); return new WhereClause(newCondition); } visitGroupByClause(clause) { const newGrouping = clause.grouping.map(item => this.visit(item)); return new GroupByClause(newGrouping); } visitHavingClause(clause) { const newCondition = this.visit(clause.condition); return new HavingClause(newCondition); } visitOrderByClause(clause) { const newOrder = clause.order.map(item => this.visit(item)); return new OrderByClause(newOrder); } visitWindowFrameClause(clause) { const newExpression = this.visit(clause.expression); return new WindowFrameClause(clause.name.name, newExpression); } visitLimitClause(clause) { const newLimit = this.visit(clause.value); return new LimitClause(newLimit); } visitForClause(clause) { return new ForClause(clause.lockMode); } visitParenExpression(expr) { const newExpression = this.visit(expr.expression); return new ParenExpression(newExpression); } visitBinaryExpression(expr) { const newLeft = this.visit(expr.left); const newRight = this.visit(expr.right); return new BinaryExpression(newLeft, expr.operator.value, newRight); } visitUnaryExpression(expr) { const newExpression = this.visit(expr.expression); return new UnaryExpression(expr.operator.value, newExpression); } visitCaseExpression(expr) { const newCondition = expr.condition ? this.visit(expr.condition) : null; const newSwitchCase = this.visit(expr.switchCase); return new CaseExpression(newCondition, newSwitchCase); } visitSwitchCaseArgument(switchCase) { const newCases = switchCase.cases.map(caseItem => this.visit(caseItem)); const newElseValue = switchCase.elseValue ? this.visit(switchCase.elseValue) : null; return new SwitchCaseArgument(newCases, newElseValue); } visitCaseKeyValuePair(pair) { const newKey = this.visit(pair.key); const newValue = this.visit(pair.value); return new CaseKeyValuePair(newKey, newValue); } visitBetweenExpression(expr) { const newExpression = this.visit(expr.expression); const newLower = this.visit(expr.lower); const newUpper = this.visit(expr.upper); return new BetweenExpression(newExpression, newLower, newUpper, expr.negated); } visitFunctionCall(func) { const newArgument = func.argument ? this.visit(func.argument) : null; const newOver = func.over ? this.visit(func.over) : null; return new FunctionCall(func.namespaces, func.name, newArgument, newOver); } visitArrayExpression(expr) { const newExpression = this.visit(expr.expression); return new ArrayExpression(newExpression); } visitTupleExpression(expr) { const newValues = expr.values.map(value => this.visit(value)); return new TupleExpression(newValues); } visitCastExpression(expr) { const newInput = this.visit(expr.input); const newCastType = this.visit(expr.castType); return new CastExpression(newInput, newCastType); } visitTypeValue(typeValue) { const newArgument = typeValue.argument ? this.visit(typeValue.argument) : null; return new TypeValue(typeValue.namespaces, typeValue.name, newArgument); } visitSelectItem(item) { var _a; const newValue = this.visit(item.value); return new SelectItem(newValue, (_a = item.identifier) === null || _a === void 0 ? void 0 : _a.name); } visitIdentifierString(ident) { // Identifiers don't have child components, so just return as-is return ident; } visitRawString(raw) { // Raw strings don't have child components, so just return as-is return raw; } visitColumnReference(column) { // Column references don't have subqueries, so just return as-is return column; } visitSourceExpression(source) { const newSource = this.visit(source.datasource); // SourceAliasEpression don't contain subqueries, so just return as-is const newAlias = source.aliasExpression; return new SourceExpression(newSource, newAlias); } visitTableSource(source) { // Table sources don't contain subqueries, so just return as-is return source; } visitParenSource(source) { const newSource = this.visit(source.source); return new ParenSource(newSource); } visitParameterExpression(param) { // Parameter expressions don't have child components, so just return as-is return param; } visitWindowFrameExpression(expr) { const newPartition = expr.partition ? this.visit(expr.partition) : null; const newOrder = expr.order ? this.visit(expr.order) : null; const newFrameSpec = expr.frameSpec ? this.visit(expr.frameSpec) : null; return new WindowFrameExpression(newPartition, newOrder, newFrameSpec); } visitWindowFrameSpec(spec) { // WindowFrameSpec is a simple value object, so return as-is return spec; } visitLiteralValue(value) { // Literal values are returned as-is return value; } visitOrderByItem(item) { const newValue = this.visit(item.value); return new OrderByItem(newValue, item.sortDirection, item.nullsPosition); } } //# sourceMappingURL=CTEDisabler.js.map