rawsql-ts
Version:
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
781 lines • 31.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParameterRemover = void 0;
const Clause_1 = require("../models/Clause");
const SelectQuery_1 = require("../models/SelectQuery");
const ValueComponent_1 = require("../models/ValueComponent");
const OperatorPrecedence_1 = require("./OperatorPrecedence");
/**
* Helper visitor to detect if a component tree contains any ParameterExpression
*/
class ParameterDetector {
constructor() {
this.handlers = new Map();
// ParameterExpression always returns true
this.handlers.set(ValueComponent_1.ParameterExpression.kind, () => true);
// Binary expressions check both sides
this.handlers.set(ValueComponent_1.BinaryExpression.kind, (expr) => this.visit(expr.left) || this.visit(expr.right));
// Parenthesized expressions check inner expression
this.handlers.set(ValueComponent_1.ParenExpression.kind, (expr) => this.visit(expr.expression));
// Unary expressions check inner expression
this.handlers.set(ValueComponent_1.UnaryExpression.kind, (expr) => this.visit(expr.expression));
// Function calls check argument if present
this.handlers.set(ValueComponent_1.FunctionCall.kind, (expr) => expr.argument ? this.visit(expr.argument) : false);
// Case expressions check condition and switch cases
this.handlers.set(ValueComponent_1.CaseExpression.kind, (expr) => {
const conditionHasParam = expr.condition ? this.visit(expr.condition) : false;
const switchCaseHasParam = this.visit(expr.switchCase);
return conditionHasParam || switchCaseHasParam;
});
// Between expressions check all three parts
this.handlers.set(ValueComponent_1.BetweenExpression.kind, (expr) => this.visit(expr.expression) || this.visit(expr.lower) || this.visit(expr.upper));
// Default case: no parameters for simple types
// (ColumnReference, LiteralValue, IdentifierString, etc.)
}
visit(component) {
const handler = this.handlers.get(component.getKind());
if (handler) {
return handler(component);
}
return false; // Default: no parameters
}
static detect(component) {
const detector = new ParameterDetector();
return detector.visit(component);
}
}
/**
* Helper to analyze SQL expression structure safely
*/
class ExpressionAnalyzer {
/**
* Check if a component is a binary expression with logical operator (AND/OR)
*/
static isLogicalBinaryExpression(component) {
if (component.getKind() !== ValueComponent_1.BinaryExpression.kind) {
return false;
}
const expr = component;
return OperatorPrecedence_1.OperatorPrecedence.isLogicalOperator(expr.operator.value);
}
/**
* Check if a component is a comparison operator
*/
static isComparisonBinaryExpression(component) {
if (component.getKind() !== ValueComponent_1.BinaryExpression.kind) {
return false;
}
const expr = component;
return OperatorPrecedence_1.OperatorPrecedence.isComparisonOperator(expr.operator.value);
}
}
/**
* Utility class to traverse and remove ParameterExpression nodes from the SQL AST.
* This removes any binary expression containing a ParameterExpression as a whole.
* For compound logical expressions (AND/OR), only the parameterized parts are removed.
* If all conditions are removed from a logical expression, the parent node is removed as well.
*/
class ParameterRemover {
constructor() {
this.visitedNodes = new Set();
this.isRootVisit = true;
this.handlers = new Map();
// Setup handlers for all component types
// 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));
// WithClause and CommonTable
this.handlers.set(Clause_1.WithClause.kind, (expr) => this.visitWithClause(expr));
this.handlers.set(Clause_1.CommonTable.kind, (expr) => this.visitCommonTable(expr));
// SelectClause and SelectItem
this.handlers.set(Clause_1.SelectClause.kind, (expr) => this.visitSelectClause(expr));
this.handlers.set(Clause_1.SelectItem.kind, (expr) => this.visitSelectItem(expr));
this.handlers.set(Clause_1.Distinct.kind, (expr) => this.visitDistinctComponent(expr));
this.handlers.set(Clause_1.DistinctOn.kind, (expr) => this.visitDistinctComponent(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));
this.handlers.set(Clause_1.SourceAliasExpression.kind, (expr) => this.visitSourceAliasExpression(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 or parameters
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));
// Other clauses
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.OrderByItem.kind, (expr) => this.visitOrderByItem(expr));
this.handlers.set(Clause_1.WindowFrameClause.kind, (expr) => this.visitWindowFrameClause(expr));
this.handlers.set(Clause_1.WindowsClause.kind, (expr) => this.visitWindowsClause(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.OffsetClause.kind, (expr) => this.visitOffsetClause(expr));
this.handlers.set(Clause_1.FetchClause.kind, (expr) => this.visitFetchClause(expr));
this.handlers.set(Clause_1.FetchExpression.kind, (expr) => this.visitFetchExpression(expr));
}
/**
* Reset the visited nodes tracking
*/
reset() {
this.visitedNodes.clear();
}
/**
* Main entry point for the visitor pattern.
* @param arg The SQL component to visit
* @returns The component with parameter expressions removed, or null if the entire component should be removed
*/
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);
// Handle null values
if (!arg) {
return null;
}
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(`[ParameterRemover] No handler for ${constructor} with kind ${kindSymbol}.`);
}
/**
* Visit SimpleSelectQuery node
*/
visitSimpleSelectQuery(query) {
const withClause = query.withClause ? this.visit(query.withClause) : null;
// SelectClause is required
if (!query.selectClause) {
throw new Error("[ParameterRemover] SimpleSelectQuery missing required selectClause");
}
const selectClause = this.visit(query.selectClause);
const fromClause = query.fromClause ? this.visit(query.fromClause) : null;
const whereClause = query.whereClause ? this.visit(query.whereClause) : null;
const groupByClause = query.groupByClause ? this.visit(query.groupByClause) : null;
const havingClause = query.havingClause ? this.visit(query.havingClause) : null;
const orderByClause = query.orderByClause ? this.visit(query.orderByClause) : null;
const windowClause = query.windowClause ? this.visit(query.windowClause) : null;
const limitClause = query.limitClause ? this.visit(query.limitClause) : null;
const offsetClause = query.offsetClause ? this.visit(query.offsetClause) : null;
const fetchClause = query.fetchClause ? this.visit(query.fetchClause) : null;
const forClause = query.forClause ? this.visit(query.forClause) : null;
return new SelectQuery_1.SimpleSelectQuery({
withClause,
selectClause,
fromClause,
whereClause,
groupByClause,
havingClause,
orderByClause,
windowClause,
limitClause,
offsetClause,
fetchClause,
forClause
});
}
/**
* Visit BinarySelectQuery node
*/
visitBinarySelectQuery(query) {
if (!query.left || !query.right) {
return null;
}
const left = this.visit(query.left);
if (!left) {
return null;
}
const right = this.visit(query.right);
if (!right) {
return null;
}
const operation = query.operator;
return new SelectQuery_1.BinarySelectQuery(left, operation.value, right);
}
/**
* Visit ValuesQuery node
*/
visitValuesQuery(query) {
// Since ValuesQuery doesn't typically contain parameters in WHERE conditions,
// we'll just return it as is for now
return query;
}
/**
* Visit WithClause node
*/
visitWithClause(clause) {
if (!clause.tables) {
return null;
}
const tables = clause.tables
.map(table => this.visit(table))
.filter(table => table !== null);
if (tables.length === 0) {
return null;
}
return new Clause_1.WithClause(clause.recursive, tables);
}
/**
* Visit CommonTable node
*/
visitCommonTable(table) {
if (!table.aliasExpression || !table.query) {
return null;
}
const aliasExpression = this.visit(table.aliasExpression);
if (!aliasExpression) {
return null;
}
const selectQuery = this.visit(table.query);
if (!selectQuery) {
return null;
}
return new Clause_1.CommonTable(selectQuery, aliasExpression, table.materialized);
}
/**
* Visit SelectClause node
*/
visitSelectClause(clause) {
if (!clause.items) {
throw new Error("[ParameterRemover] SelectClause missing required items");
}
const items = clause.items
.map(item => this.visit(item))
.filter(item => item !== null);
const distinct = clause.distinct ? this.visit(clause.distinct) : null;
if (items.length === 0) {
throw new Error("[ParameterRemover] SelectClause must have at least one item");
}
return new Clause_1.SelectClause(items, distinct);
}
/**
* Visit SelectItem node
*/
visitSelectItem(item) {
var _a;
if (!item.value) {
return null;
}
const value = this.visit(item.value);
if (!value) {
return null;
}
return new Clause_1.SelectItem(value, ((_a = item.identifier) === null || _a === void 0 ? void 0 : _a.name) || null);
}
// Simple visitor methods that return the node unchanged
visitIdentifierString(identifier) { return identifier; }
visitRawString(str) { return str; }
visitColumnReference(ref) { return ref; }
visitParameterExpression(param) { return null; }
visitLiteralValue(literal) { return literal; }
visitTableSource(source) { return source; }
visitForClause(clause) { return clause; }
visitDistinctComponent(component) { return component; }
/**
* Visit SourceExpression node
*/
visitSourceExpression(source) {
if (!source.datasource) {
return source; // Return as is instead of null
}
const sourceComponent = this.visit(source.datasource);
if (!sourceComponent) {
return source; // Return as is instead of null
}
return new Clause_1.SourceExpression(sourceComponent, source.aliasExpression);
}
/**
* Visit ParenSource node
*/
visitParenSource(source) {
return new Clause_1.ParenSource(this.visit(source.source));
}
/**
* Visit SourceAliasExpression node
*/
visitSourceAliasExpression(expr) {
const table = expr.table;
const columns = expr.columns;
const columnNames = columns ? columns.map(col => col.name) : null;
return new Clause_1.SourceAliasExpression(table.name, columnNames);
}
/**
* Visit SubQuerySource node
*/
visitSubQuerySource(source) {
if (!source.query) {
return null;
}
const query = this.visit(source.query);
if (!query) {
return null;
}
return new Clause_1.SubQuerySource(query);
}
/**
* Visit InlineQuery node
*/
visitInlineQuery(query) {
if (!query.selectQuery) {
return null;
}
const selectQuery = this.visit(query.selectQuery);
if (!selectQuery) {
return null;
}
return new ValueComponent_1.InlineQuery(selectQuery);
}
/**
* Visit FromClause node
*/
visitFromClause(clause) {
if (!clause.source) {
return clause; // Return as is instead of null
}
// Always keep the source, even if something inside might change
const source = this.visit(clause.source);
let joins = null;
if (clause.joins) {
const processedJoins = clause.joins
.map(join => this.visit(join))
.filter(join => join !== null);
if (processedJoins.length > 0) {
joins = processedJoins;
}
}
return new Clause_1.FromClause(source || clause.source, joins);
}
/**
* Visit JoinClause node
*/
visitJoinClause(clause) {
if (!clause.source) {
return null;
}
const source = this.visit(clause.source);
if (!source) {
return null;
}
const condition = clause.condition ? this.visit(clause.condition) : null;
return new Clause_1.JoinClause(clause.joinType.value, source, condition, clause.lateral);
}
/**
* Visit JoinOnClause node
*/
visitJoinOnClause(clause) {
const condition = this.visit(clause.condition);
// If condition has been removed (contains only parameters), return null
if (!condition) {
return null;
}
return new Clause_1.JoinOnClause(condition);
}
/**
* Visit JoinUsingClause node
*/
visitJoinUsingClause(clause) {
return clause;
}
/**
* Visit WhereClause node - key method for parameter removal
*/
visitWhereClause(clause) {
const condition = this.visit(clause.condition);
// If the entire condition has been removed (contains only parameters), return null
if (!condition) {
return null;
}
return new Clause_1.WhereClause(condition);
}
/**
* Visit ParenExpression node
*/
visitParenExpression(expr) {
const innerExpression = this.visit(expr.expression);
// If the inner expression has been removed (contains only parameters), return null
if (!innerExpression) {
return null;
}
return new ValueComponent_1.ParenExpression(innerExpression);
}
/**
* Visit BinaryExpression node - improved logic for right-associative parser structure
*/
visitBinaryExpression(expr) {
const operator = expr.operator.value.toLowerCase();
if (OperatorPrecedence_1.OperatorPrecedence.isLogicalOperator(operator)) {
// Handle logical operators normally
const left = this.visit(expr.left);
const right = this.visit(expr.right);
if (!left && !right) {
return null;
}
if (!left && right) {
return right;
}
if (left && !right) {
return left;
}
return new ValueComponent_1.BinaryExpression(left, expr.operator.value, right);
}
else {
// For comparison operators, handle right-associative parser structure
return this.handleComparisonExpression(expr);
}
}
/**
* Handle comparison expressions, accounting for right-associative parser structure
*/
handleComparisonExpression(expr) {
const left = this.visit(expr.left);
// Check if the right side is a logical expression (AND/OR) using type-safe analysis
if (ExpressionAnalyzer.isLogicalBinaryExpression(expr.right)) {
// This is the problematic case: comparison = (logical expression)
// We need to restructure this as: (comparison) logical (other parts)
return this.restructureComparisonWithLogical(expr, left);
}
// Normal comparison processing
const right = this.visit(expr.right);
if (!left || !right) {
return null;
}
return new ValueComponent_1.BinaryExpression(left, expr.operator.value, right);
}
/**
* Restructure expressions like "id = (1 AND ...)" to "(id = 1) AND ..."
*/
restructureComparisonWithLogical(expr, processedLeft) {
if (!processedLeft) {
return null;
}
const rightBinary = expr.right;
const logicalOperator = rightBinary.operator.value;
// Process the logical expression's left side as the right side of our comparison
const comparisonRight = this.visit(rightBinary.left);
if (!comparisonRight) {
// If the comparison right side contains only parameters,
// try to process the logical expression's right side
return this.visit(rightBinary.right);
}
// Create the restructured comparison: "id = 1"
const restructuredComparison = new ValueComponent_1.BinaryExpression(processedLeft, expr.operator.value, comparisonRight);
// Process the remaining logical expression's right side
const logicalRight = this.visit(rightBinary.right);
if (!logicalRight) {
// Only the left comparison is valid
return restructuredComparison;
}
// Combine: "(id = 1) AND (remaining expression)"
return new ValueComponent_1.BinaryExpression(restructuredComparison, logicalOperator, logicalRight);
}
/**
* Check if an operator is a logical operator */
/**
* Check if the resulting expression would be nonsensical
* This is a heuristic to detect cases like "name = age > 18"
*/
wouldCreateNonsensicalExpression(left, operator, right) {
// Only apply this check for simple cases where we have a direct comparison operator
// followed by another comparison operator in the right side
if (OperatorPrecedence_1.OperatorPrecedence.isComparisonOperator(operator) && ExpressionAnalyzer.isComparisonBinaryExpression(right)) {
const rightBinary = right;
if (rightBinary.operator && OperatorPrecedence_1.OperatorPrecedence.isComparisonOperator(rightBinary.operator.value)) {
// Additional check: make sure this isn't a legitimate nested case
// If the left side is a simple column and the right side is a comparison,
// this is likely nonsensical (like "name = age > 18")
if (left.getKind().toString().includes('ColumnReference')) {
return true;
}
}
}
return false;
}
/**
* Check if a ValueComponent contains a ParameterExpression anywhere in its tree
*/
containsParameter(component) {
return ParameterDetector.detect(component);
}
/**
* Visit UnaryExpression node
*/
visitUnaryExpression(expr) {
const expression = this.visit(expr.expression);
// If the expression has been removed (contains only parameters), return null
if (!expression) {
return null;
}
return new ValueComponent_1.UnaryExpression(expr.operator.value, expression);
}
/**
* Visit CaseExpression node
*/
visitCaseExpression(expr) {
const condition = expr.condition ? this.visit(expr.condition) : null;
const switchCase = this.visit(expr.switchCase);
// If switchCase has been removed (contains only parameters), return null
if (!switchCase) {
return null;
}
return new ValueComponent_1.CaseExpression(condition, switchCase);
}
/**
* Visit CaseKeyValuePair node
*/
visitCaseKeyValuePair(pair) {
// If either key or value contains parameters, remove the entire pair
if (this.containsParameter(pair.key) || this.containsParameter(pair.value)) {
return null;
}
const key = this.visit(pair.key);
const value = this.visit(pair.value);
return new ValueComponent_1.CaseKeyValuePair(key, value);
}
/**
* Visit SwitchCaseArgument node
*/
visitSwitchCaseArgument(arg) {
// Process all case pairs, filter out null results
const cases = arg.cases
.map(caseItem => this.visit(caseItem))
.filter(caseItem => caseItem !== null);
// Process the else value if it exists
const elseValue = arg.elseValue ? this.visit(arg.elseValue) : null;
// If no cases remain and no else value, remove the entire switch case
if (cases.length === 0 && !elseValue) {
return null;
}
return new ValueComponent_1.SwitchCaseArgument(cases, elseValue);
}
/**
* Visit BetweenExpression node
*/
visitBetweenExpression(expr) {
// If any part of the expression contains a parameter, remove the entire expression
if (this.containsParameter(expr.expression) ||
this.containsParameter(expr.lower) ||
this.containsParameter(expr.upper)) {
return null;
}
const expression = this.visit(expr.expression);
const lower = this.visit(expr.lower);
const upper = this.visit(expr.upper);
return new ValueComponent_1.BetweenExpression(expression, lower, upper, expr.negated);
}
/**
* Visit FunctionCall node
*/
visitFunctionCall(call) {
const argument = call.argument ? this.visit(call.argument) : null;
const over = call.over ? this.visit(call.over) : null;
return new ValueComponent_1.FunctionCall(call.qualifiedName.namespaces, call.qualifiedName.name, argument, over);
}
/**
* Visit ArrayExpression node
*/
visitArrayExpression(expr) {
const expression = this.visit(expr.expression);
// If the expression has been removed (contains only parameters), return null
if (!expression) {
return null;
}
return new ValueComponent_1.ArrayExpression(expression);
}
/**
* Visit TupleExpression node
*/
visitTupleExpression(expr) {
const values = expr.values
.map(value => this.visit(value))
.filter(value => value !== null);
return new ValueComponent_1.TupleExpression(values);
}
/**
* Visit CastExpression node
*/
visitCastExpression(expr) {
// If the input contains a parameter, remove the entire expression
if (this.containsParameter(expr.input)) {
return null;
}
const input = this.visit(expr.input);
const castType = this.visit(expr.castType);
// If the input has been removed, return null
if (!input) {
return null;
}
return new ValueComponent_1.CastExpression(input, castType);
}
/**
* Visit WindowFrameExpression node
*/
visitWindowFrameExpression(expr) {
const partition = expr.partition ? this.visit(expr.partition) : null;
const order = expr.order ? this.visit(expr.order) : null;
const frameSpec = expr.frameSpec ? this.visit(expr.frameSpec) : null;
return new ValueComponent_1.WindowFrameExpression(partition, order, frameSpec);
}
/**
* Visit WindowFrameSpec node
*/
visitWindowFrameSpec(spec) {
return spec;
}
/**
* Visit TypeValue node
*/
visitTypeValue(type) {
const argument = type.argument ? this.visit(type.argument) : null;
return new ValueComponent_1.TypeValue(type.qualifiedName.namespaces, type.qualifiedName.name, argument);
}
/**
* Visit GroupByClause node
*/
visitGroupByClause(clause) {
if (!clause.grouping || clause.grouping.length === 0) {
return null;
}
const grouping = clause.grouping
.map(expr => this.visit(expr))
.filter(expr => expr !== null);
if (grouping.length === 0) {
return null;
}
return new Clause_1.GroupByClause(grouping);
}
/**
* Visit HavingClause node
*/
visitHavingClause(clause) {
const condition = this.visit(clause.condition);
// If the condition has been removed (contains only parameters), return null
if (!condition) {
return null;
}
return new Clause_1.HavingClause(condition);
}
/**
* Visit OrderByClause node
*/
visitOrderByClause(clause) {
const items = clause.order
.map((item) => this.visit(item))
.filter((item) => item !== null);
return new Clause_1.OrderByClause(items);
}
/**
* Visit OrderByItem node
*/
visitOrderByItem(item) {
const value = this.visit(item.value);
return new Clause_1.OrderByItem(value, item.sortDirection, item.nullsPosition);
}
/**
* Visit WindowFrameClause node
*/
visitWindowFrameClause(clause) {
const expression = this.visit(clause.expression);
return new Clause_1.WindowFrameClause(clause.name.name, expression);
}
/**
* Visit WindowsClause node
*/
visitWindowsClause(clause) {
const windows = clause.windows.map(window => this.visit(window));
return new Clause_1.WindowsClause(windows);
}
/**
* Visit LimitClause node
*/
visitLimitClause(clause) {
const value = this.visit(clause.value);
return new Clause_1.LimitClause(value);
}
/**
* Visit OffsetClause node
*/
visitOffsetClause(clause) {
const value = this.visit(clause.value);
return new Clause_1.OffsetClause(value);
}
/**
* Visit FetchClause node
*/
visitFetchClause(clause) {
const expression = this.visit(clause.expression);
return new Clause_1.FetchClause(expression);
}
/**
* Visit FetchExpression node
*/
visitFetchExpression(expression) {
const count = this.visit(expression.count);
return new Clause_1.FetchExpression(expression.type, count, expression.unit);
}
/**
* Static method to apply parameter removal transformation on an SQL AST
* @param node The SQL AST node to transform
* @returns The transformed SQL AST with parameter expressions removed
*/
static remove(node) {
const remover = new ParameterRemover();
return remover.visit(node);
}
}
exports.ParameterRemover = ParameterRemover;
//# sourceMappingURL=ParameterRemover.js.map