rawsql-ts
Version:
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
332 lines • 16.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CTEDisabler = void 0;
const Clause_1 = require("../models/Clause");
const SelectQuery_1 = require("../models/SelectQuery");
const ValueComponent_1 = require("../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.
*/
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(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));
// SelectComponent types
this.handlers.set(Clause_1.SelectItem.kind, (expr) => this.visitSelectItem(expr));
// Identifiers and raw strings
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.ArrayQueryExpression.kind, (expr) => this.visitArrayQueryExpression(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));
// 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));
}
/**
* 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 Clause_1.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 SelectQuery_1.ValuesQuery(newTuples);
}
visitSelectClause(clause) {
const newItems = clause.items.map(item => {
return this.visit(item);
});
return new Clause_1.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 Clause_1.FromClause(newSource, newJoins);
}
visitSubQuerySource(subQuery) {
const newQuery = this.visit(subQuery.query);
return new Clause_1.SubQuerySource(newQuery);
}
visitInlineQuery(inlineQuery) {
const newQuery = this.visit(inlineQuery.selectQuery);
return new ValueComponent_1.InlineQuery(newQuery);
}
visitJoinClause(joinClause) {
const newSource = this.visit(joinClause.source);
const newCondition = joinClause.condition ? this.visit(joinClause.condition) : null;
return new Clause_1.JoinClause(joinClause.joinType.value, newSource, newCondition, joinClause.lateral);
}
visitJoinOnClause(joinOn) {
const newCondition = this.visit(joinOn.condition);
return new Clause_1.JoinOnClause(newCondition);
}
visitJoinUsingClause(joinUsing) {
const newCondition = this.visit(joinUsing.condition);
return new Clause_1.JoinUsingClause(newCondition);
}
visitWhereClause(whereClause) {
const newCondition = this.visit(whereClause.condition);
return new Clause_1.WhereClause(newCondition);
}
visitGroupByClause(clause) {
const newGrouping = clause.grouping.map(item => this.visit(item));
return new Clause_1.GroupByClause(newGrouping);
}
visitHavingClause(clause) {
const newCondition = this.visit(clause.condition);
return new Clause_1.HavingClause(newCondition);
}
visitOrderByClause(clause) {
const newOrder = clause.order.map(item => this.visit(item));
return new Clause_1.OrderByClause(newOrder);
}
visitWindowFrameClause(clause) {
const newExpression = this.visit(clause.expression);
return new Clause_1.WindowFrameClause(clause.name.name, newExpression);
}
visitLimitClause(clause) {
const newLimit = this.visit(clause.value);
return new Clause_1.LimitClause(newLimit);
}
visitForClause(clause) {
return new Clause_1.ForClause(clause.lockMode);
}
visitParenExpression(expr) {
const newExpression = this.visit(expr.expression);
return new ValueComponent_1.ParenExpression(newExpression);
}
visitBinaryExpression(expr) {
const newLeft = this.visit(expr.left);
const newRight = this.visit(expr.right);
return new ValueComponent_1.BinaryExpression(newLeft, expr.operator.value, newRight);
}
visitUnaryExpression(expr) {
const newExpression = this.visit(expr.expression);
return new ValueComponent_1.UnaryExpression(expr.operator.value, newExpression);
}
visitCaseExpression(expr) {
const newCondition = expr.condition ? this.visit(expr.condition) : null;
const newSwitchCase = this.visit(expr.switchCase);
return new ValueComponent_1.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 ValueComponent_1.SwitchCaseArgument(newCases, newElseValue);
}
visitCaseKeyValuePair(pair) {
const newKey = this.visit(pair.key);
const newValue = this.visit(pair.value);
return new ValueComponent_1.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 ValueComponent_1.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 ValueComponent_1.FunctionCall(func.namespaces, func.name, newArgument, newOver);
}
visitArrayExpression(expr) {
const newExpression = this.visit(expr.expression);
return new ValueComponent_1.ArrayExpression(newExpression);
}
visitArrayQueryExpression(expr) {
const newQuery = this.visit(expr.query);
return new ValueComponent_1.ArrayQueryExpression(newQuery);
}
visitTupleExpression(expr) {
const newValues = expr.values.map(value => this.visit(value));
return new ValueComponent_1.TupleExpression(newValues);
}
visitCastExpression(expr) {
const newInput = this.visit(expr.input);
const newCastType = this.visit(expr.castType);
return new ValueComponent_1.CastExpression(newInput, newCastType);
}
visitTypeValue(typeValue) {
const newArgument = typeValue.argument ? this.visit(typeValue.argument) : null;
return new ValueComponent_1.TypeValue(typeValue.namespaces, typeValue.name, newArgument);
}
visitSelectItem(item) {
var _a;
const newValue = this.visit(item.value);
return new Clause_1.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 Clause_1.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 Clause_1.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 ValueComponent_1.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 Clause_1.OrderByItem(newValue, item.sortDirection, item.nullsPosition);
}
}
exports.CTEDisabler = CTEDisabler;
//# sourceMappingURL=CTEDisabler.js.map