UNPKG

rawsql-ts

Version:

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

1,117 lines 72.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.optimizeStaticPredicatePlacement = exports.planStaticPredicatePlacement = exports.StaticPredicatePlacementOptimizer = void 0; const Clause_1 = require("../models/Clause"); const SelectQuery_1 = require("../models/SelectQuery"); const ValueComponent_1 = require("../models/ValueComponent"); const SelectQueryParser_1 = require("../parsers/SelectQueryParser"); const SqlComponentFormatter_1 = require("./SqlComponentFormatter"); const SelectOutputCollector_1 = require("./SelectOutputCollector"); const TopLevelAndConditionDeduper_1 = require("./TopLevelAndConditionDeduper"); const PredicateExpressionUtils_1 = require("./PredicateExpressionUtils"); const VOLATILE_OR_UNSUPPORTED_FUNCTION_REASON = "Predicate contains a function call; volatile and expression predicates are not moved in the safe-only implementation."; class StaticPredicatePlacementOptimizer { plan(input, options = {}) { var _a, _b, _c; const parsed = this.parseInput(input, options); const warnings = [...parsed.warnings]; const errors = [...parsed.errors]; if (!parsed.query) { return this.buildResult({ query: null, sql: parsed.sql, applied: [], skipped: [], warnings, errors, dryRun: (_a = options.dryRun) !== null && _a !== void 0 ? _a : true, formatterGeneratedSource: parsed.formatterGeneratedSource }); } if (!(parsed.query instanceof SelectQuery_1.SimpleSelectQuery)) { warnings.push({ code: "UNSUPPORTED_ROOT_QUERY", message: "Static predicate placement currently supports only SimpleSelectQuery roots." }); return this.buildResult({ query: parsed.query, sql: parsed.sql, applied: [], skipped: [], warnings, errors, dryRun: (_b = options.dryRun) !== null && _b !== void 0 ? _b : true, formatterGeneratedSource: parsed.formatterGeneratedSource }); } const query = parsed.query; const applied = []; const skipped = []; this.placePredicatesInScopes(query, options, applied, skipped); // API output shape review: keep result.sql/result.query compatibility while expanding scope-aware placement. const sql = applied.length > 0 || (0, SqlComponentFormatter_1.hasSqlComponentFormatOverride)(options) ? (0, SqlComponentFormatter_1.formatSqlComponent)(query, options) : parsed.sql; return this.buildResult({ query, sql, applied, skipped, warnings, errors, dryRun: (_c = options.dryRun) !== null && _c !== void 0 ? _c : true, formatterGeneratedSource: parsed.formatterGeneratedSource }); } placePredicatesInScopes(contextRoot, options, applied, skipped) { var _a; const queue = [{ query: contextRoot, scopeId: "scope:root" }]; let processed = 0; while (queue.length > 0) { processed += 1; if (processed > 1000) { skipped.push({ predicateSql: "", scopeId: "scope:root", code: "RECURSIVE_PLACEMENT_LIMIT", reason: "Static predicate recursive placement stopped after reaching the safety iteration limit." }); return; } const work = queue.shift(); const movedTerms = []; const reportSkipped = work.pendingTerms === undefined; const terms = (_a = work.pendingTerms) !== null && _a !== void 0 ? _a : (work.query.whereClause ? (0, PredicateExpressionUtils_1.collectTopLevelAndTerms)(work.query.whereClause.condition) : []); for (const term of terms) { const candidate = this.analyzeCandidate(work.query, term, options); if (!candidate) { continue; } if ("code" in candidate) { if (reportSkipped) { skipped.push(this.makeSkipped(term, work.scopeId, candidate, options)); } continue; } const target = this.resolveTarget(contextRoot, work.query, candidate); if ("code" in target) { if (reportSkipped) { skipped.push(this.makeSkipped(term, work.scopeId, target, options)); } continue; } let appliedReason = ""; if (target.kind === "join_on") { this.appendJoinOnPredicate(target.join, candidate.expression, options); appliedReason = target.reason; } else if (target.kind === "simple") { const targetColumns = this.resolveTargetColumns(contextRoot, target.query, candidate.references); if ("code" in targetColumns) { if (reportSkipped) { skipped.push(this.makeSkipped(term, work.scopeId, targetColumns, options)); } continue; } const placement = this.resolveTargetPlacement(contextRoot, target.query, targetColumns); if ("code" in placement) { if (reportSkipped) { skipped.push(this.makeSkipped(term, work.scopeId, placement, options)); } continue; } const movedPredicate = this.rebasePredicate(candidate.expression, targetColumns, options); target.query.appendWhere(movedPredicate); (0, TopLevelAndConditionDeduper_1.dedupeWhereTopLevelAndConditions)(target.query, options); appliedReason = work.scopeId === "scope:root" ? placement.reason : `${placement.reason} Predicate was safely rebased from a previously moved predicate.`; const retainedTerms = target.query.whereClause ? (0, PredicateExpressionUtils_1.collectTopLevelAndTerms)(target.query.whereClause.condition) : []; if (retainedTerms.includes(movedPredicate)) { queue.push({ query: target.query, scopeId: target.scopeId, pendingTerms: [movedPredicate] }); } } else { const placements = []; let skip = null; for (const branch of target.branches) { const placement = this.resolveTargetPlacement(contextRoot, branch.query, branch.targetColumns); if ("code" in placement) { skip = placement; break; } placements.push(placement); } if (skip) { if (reportSkipped) { skipped.push(this.makeSkipped(term, work.scopeId, skip, options)); } continue; } for (const branch of target.branches) { const movedPredicate = this.rebasePredicate(candidate.expression, branch.targetColumns, options); branch.query.appendWhere(movedPredicate); (0, TopLevelAndConditionDeduper_1.dedupeWhereTopLevelAndConditions)(branch.query, options); } appliedReason = placements.some(item => /group by/i.test(item.reason)) ? "Predicate is distributed to every UNION branch by output column position; grouped branches only receive GROUP BY-key predicates." : "Predicate is distributed to every UNION branch by output column position before unsafe query boundaries."; } movedTerms.push(term); applied.push({ kind: "move_static_predicate", predicateSql: candidate.predicateSql, fromScopeId: work.scopeId, toScopeId: target.scopeId, reason: appliedReason, columnReferences: candidate.references.map(PredicateExpressionUtils_1.columnReferenceText) }); } (0, PredicateExpressionUtils_1.rebuildWhereWithoutTerms)(work.query, new Set(movedTerms)); } } optimize(input, options = {}) { var _a; return this.plan(input, { ...options, dryRun: (_a = options.dryRun) !== null && _a !== void 0 ? _a : false }); } parseInput(input, options) { const warnings = []; const errors = []; try { const sourceSql = typeof input === "string" ? input : (0, SqlComponentFormatter_1.formatSqlComponent)(input, options); if (typeof input !== "string" && options.cloneInput === false) { return { query: input, sql: sourceSql, formatterGeneratedSource: false, warnings, errors }; } if (typeof input !== "string") { warnings.push({ code: "AST_INPUT_FORMATTED", message: "AST input is cloned through formatter output so the caller-owned query is not mutated." }); } return { query: SelectQueryParser_1.SelectQueryParser.parse(sourceSql), sql: sourceSql, formatterGeneratedSource: typeof input !== "string", warnings, errors }; } catch (error) { const detail = error instanceof Error ? error.message : String(error); errors.push({ code: "PARSE_FAILED", message: "Static predicate placement could not parse the input SQL.", detail }); return { query: null, sql: typeof input === "string" ? input : "", formatterGeneratedSource: typeof input !== "string", warnings, errors }; } } analyzeCandidate(root, expression, options) { if (this.collectParameterNames(expression).length > 0) { return null; } const unsupported = this.findUnsupportedExpression(expression, this.isExistsPredicate(expression)); if (unsupported) { return unsupported; } const references = this.collectRootColumnReferences(root, expression); if (references.length === 0) { return { code: "NO_COLUMN_REFERENCE", reason: "Static predicate has no outer column reference to anchor the move." }; } return { expression, predicateSql: (0, SqlComponentFormatter_1.formatSqlComponent)(expression, options), references }; } isExistsPredicate(expression) { const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(expression); if (!(candidate instanceof ValueComponent_1.UnaryExpression)) { return false; } const operator = candidate.operator.value.trim().toLowerCase(); return operator === "exists" && (0, PredicateExpressionUtils_1.unwrapParens)(candidate.expression) instanceof ValueComponent_1.InlineQuery; } findUnsupportedExpression(expression, allowExistsSubquery) { let found = null; const visitSelect = (query) => { var _a, _b, _c, _d; if (found) { return; } if (query instanceof SelectQuery_1.BinarySelectQuery) { found = { code: "UNION_BOUNDARY", reason: "Static predicate would need distribution into a UNION branch, which is unsupported." }; return; } if (!(query instanceof SelectQuery_1.SimpleSelectQuery)) { found = { code: "UNSUPPORTED_SUBQUERY", reason: "Static predicate contains a non-simple subquery, which is unsupported." }; return; } query.selectClause.items.forEach(item => visit(item.value)); for (const join of (_b = (_a = query.fromClause) === null || _a === void 0 ? void 0 : _a.joins) !== null && _b !== void 0 ? _b : []) { if (join.condition) { visit(join.condition.condition); } const source = join.source.datasource; if (source instanceof Clause_1.SubQuerySource) { visitSelect(source.query); } } if (query.whereClause) { visit(query.whereClause.condition); } if (query.havingClause) { visit(query.havingClause.condition); } for (const cte of (_d = (_c = query.withClause) === null || _c === void 0 ? void 0 : _c.tables) !== null && _d !== void 0 ? _d : []) { if (cte.query instanceof SelectQuery_1.SimpleSelectQuery || cte.query instanceof SelectQuery_1.BinarySelectQuery) { visitSelect(cte.query); } } }; const visit = (value) => { if (found) { return; } const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(value); if (candidate instanceof ValueComponent_1.BinaryExpression) { visit(candidate.left); visit(candidate.right); return; } if (candidate instanceof ValueComponent_1.FunctionCall) { found = { code: "FUNCTION_PREDICATE_UNSUPPORTED", reason: VOLATILE_OR_UNSUPPORTED_FUNCTION_REASON }; return; } if (candidate instanceof ValueComponent_1.CaseExpression) { found = { code: "CASE_PREDICATE_UNSUPPORTED", reason: "CASE predicates are not moved in the first safe-only implementation." }; return; } if (candidate instanceof ValueComponent_1.InlineQuery) { if (!allowExistsSubquery) { found = { code: "SUBQUERY_PREDICATE_UNSUPPORTED", reason: "Subquery predicates are only moved when they are simple EXISTS predicates." }; return; } visitSelect(candidate.selectQuery); return; } if (candidate instanceof ValueComponent_1.ArrayQueryExpression) { found = { code: "SUBQUERY_PREDICATE_UNSUPPORTED", reason: "Array subquery predicates are not moved in the first safe-only implementation." }; return; } if (candidate instanceof ValueComponent_1.UnaryExpression) { visit(candidate.expression); return; } if (candidate instanceof ValueComponent_1.CastExpression) { visit(candidate.input); return; } if (candidate instanceof ValueComponent_1.JsonPredicateExpression) { visit(candidate.expression); return; } if (candidate instanceof ValueComponent_1.ArrayExpression) { visit(candidate.expression); return; } if (candidate instanceof ValueComponent_1.ValueList) { candidate.values.forEach(visit); return; } if (candidate instanceof ValueComponent_1.TupleExpression) { candidate.values.forEach(visit); return; } if (candidate instanceof ValueComponent_1.TypeValue && candidate.argument) { visit(candidate.argument); } }; visit(expression); return found; } resolveTarget(contextRoot, currentQuery, candidate) { var _a; const boundary = this.findRootQueryBoundary(currentQuery); if (boundary) { return boundary; } if (!currentQuery.fromClause) { return { code: "NO_FROM_CLAUSE", reason: "Predicate has no FROM source that can receive it safely." }; } const bindings = []; for (const reference of candidate.references) { const binding = this.resolveSourceBinding(contextRoot, currentQuery, reference); if ("code" in binding) { return binding; } if (!bindings.some(item => item.source === binding.source)) { bindings.push(binding); } } if (bindings.length !== 1) { return { code: "MULTIPLE_SOURCE_REFERENCES", reason: "Static predicate references multiple source query blocks; moving it may change semantics." }; } const binding = bindings[0]; if ((_a = binding.join) === null || _a === void 0 ? void 0 : _a.lateral) { return { code: "LATERAL_JOIN_BOUNDARY", reason: "Predicate crosses a LATERAL JOIN boundary; moving it may change semantics." }; } const nullableSide = this.findNullableSideBoundary(currentQuery.fromClause, binding); if (nullableSide) { return nullableSide; } const upstream = this.resolveUpstreamQuery(contextRoot, binding, candidate.references); if ("code" in upstream) { if (upstream.code === "NO_SAFE_UPSTREAM_QUERY") { const joinOnTarget = this.resolveBaseTableJoinOnTarget(contextRoot, currentQuery, binding); if (!("code" in joinOnTarget)) { return joinOnTarget; } return joinOnTarget; } return upstream; } return upstream; } findRootQueryBoundary(query) { if (this.hasDistinctOnBoundary(query)) { return { code: "DISTINCT_BOUNDARY", reason: "Predicate crosses DISTINCT ON boundary; moving it may change semantics." }; } if (this.hasWindowUsage(query)) { return { code: "WINDOW_BOUNDARY", reason: "Predicate crosses WINDOW boundary; moving it may change semantics." }; } return null; } resolveTargetPlacement(contextRoot, query, targetColumns) { const hasOrdinaryDistinct = this.hasOrdinaryDistinct(query); if (this.hasDistinctOnBoundary(query)) { return { code: "DISTINCT_BOUNDARY", reason: "Predicate crosses DISTINCT ON boundary; moving it may change semantics." }; } if (this.hasWindowUsage(query)) { return { code: "WINDOW_BOUNDARY", reason: "Predicate crosses WINDOW boundary; moving it may change semantics." }; } if (query.limitClause || query.offsetClause || query.fetchClause) { return { code: "ROW_LIMIT_BOUNDARY", reason: "Predicate crosses LIMIT/OFFSET/FETCH boundary; moving it may change row selection semantics." }; } if (query.fromClause) { const nullableSide = this.findNullableSideBoundaryForTargetColumns(contextRoot, query, targetColumns); if (nullableSide) { return nullableSide; } } if (query.groupByClause) { const allReferencesAreGroupKeys = targetColumns.every(item => this.isGroupKeyColumn(query, item.targetColumn)); if (!allReferencesAreGroupKeys) { return { code: "GROUP_BY_BOUNDARY", reason: "Predicate references a target column that is not proven to be a GROUP BY key." }; } return { reason: "Predicate references only GROUP BY keys; it is moved into pre-aggregation WHERE." }; } if (query.havingClause) { return { code: "GROUP_BY_BOUNDARY", reason: "Predicate crosses HAVING aggregation boundary; moving it may change semantics." }; } if (hasOrdinaryDistinct) { return { reason: "Predicate references direct ordinary DISTINCT output columns; it is moved into the DISTINCT input WHERE." }; } return { reason: "All outer references resolve to direct upstream outputs before unsafe query boundaries." }; } findNullableSideBoundaryForTargetColumns(contextRoot, query, targetColumns) { if (!query.fromClause) { return null; } for (const item of targetColumns) { const binding = this.resolveSourceBinding(contextRoot, query, item.targetColumn); if ("code" in binding) { return binding; } const nullableSide = this.findNullableSideBoundary(query.fromClause, binding); if (nullableSide) { return nullableSide; } } return null; } resolveSourceBinding(contextRoot, query, column) { const fromClause = query.fromClause; if (!fromClause) { return { code: "NO_FROM_CLAUSE", reason: "Predicate has no FROM source that can receive it safely." }; } const bindings = this.getSourceBindings(fromClause); const namespace = column.getNamespace(); const columnName = column.column.name; if (namespace) { const matches = bindings.filter(binding => (0, PredicateExpressionUtils_1.identifiersEqual)(binding.alias, namespace)); if (matches.length !== 1) { return { code: "AMBIGUOUS_COLUMN_SOURCE", reason: `Column source alias '${column.getNamespace()}' is not uniquely resolvable.` }; } const matchCount = this.getOutputColumnMatchCount(contextRoot, matches[0], columnName); if (matchCount === 0 && this.isBaseTableBinding(contextRoot, matches[0])) { return matches[0]; } if (matchCount === 0) { return { code: "COLUMN_NOT_AVAILABLE_UPSTREAM", reason: `Column '${(0, PredicateExpressionUtils_1.columnReferenceText)(column)}' is not a direct output of the referenced source.` }; } if (matchCount > 1) { return { code: "AMBIGUOUS_COLUMN_REFERENCE", reason: `Column '${(0, PredicateExpressionUtils_1.columnReferenceText)(column)}' resolves to multiple outputs in the referenced source.` }; } return matches[0]; } const matches = []; for (const binding of bindings) { const matchCount = this.getOutputColumnMatchCount(contextRoot, binding, columnName); if (matchCount > 1) { return { code: "AMBIGUOUS_COLUMN_REFERENCE", reason: `Column '${columnName}' resolves to multiple outputs in source '${binding.alias}'.` }; } if (matchCount === 1) { matches.push(binding); } } if (matches.length !== 1) { return { code: "AMBIGUOUS_COLUMN_REFERENCE", reason: matches.length === 0 ? `Column '${columnName}' is not uniquely resolvable to a safe upstream source.` : `Column '${columnName}' is ambiguous across source query blocks.` }; } return matches[0]; } resolveUpstreamQuery(root, binding, references) { const source = binding.source.datasource; if (source instanceof Clause_1.SubQuerySource) { if (source.query instanceof SelectQuery_1.SimpleSelectQuery) { return { kind: "simple", query: source.query, scopeId: `subquery:${binding.alias}`, sourceBinding: binding }; } if (source.query instanceof SelectQuery_1.BinarySelectQuery) { return this.resolveUnionTarget(root, source.query, `subquery:${binding.alias}`, references); } return { code: "UNION_BOUNDARY", reason: "Predicate would need distribution into a UNION or non-simple subquery, which is unsupported." }; } if (!(source instanceof Clause_1.TableSource)) { return { code: "UNSUPPORTED_SOURCE", reason: "Only CTE and simple derived-table sources can receive moved static predicates." }; } const cteName = source.table.name; const commonTable = this.findCte(root, cteName); if (!commonTable) { return { code: "NO_SAFE_UPSTREAM_QUERY", reason: "The referenced source is a base table, so there is no upstream query block to move into." }; } const referenceCount = this.countTableSourceReferences(root, cteName); if (referenceCount !== 1) { return { code: "CTE_REUSE_UNSUPPORTED", reason: `CTE '${cteName}' is referenced ${referenceCount} times; moving a predicate into it may affect other consumers.` }; } if (commonTable.query instanceof SelectQuery_1.BinarySelectQuery) { return this.resolveUnionTarget(root, commonTable.query, `cte:${commonTable.getSourceAliasName()}`, references); } if (!(commonTable.query instanceof SelectQuery_1.SimpleSelectQuery)) { return { code: "UNSUPPORTED_CTE_QUERY", reason: "Writable or non-select CTE bodies are not moved into by static predicate placement." }; } return { kind: "simple", query: commonTable.query, scopeId: `cte:${commonTable.getSourceAliasName()}`, sourceBinding: binding, cteName: commonTable.getSourceAliasName() }; } resolveUnionTarget(root, query, scopeId, references) { const branches = this.collectUnionBranches(query); if ("code" in branches) { return branches; } const outputIndexes = new Map(); for (const reference of references) { const outputIndex = this.resolveUnionOutputIndex(root, branches[0], reference.column.name); if ("code" in outputIndex) { return outputIndex; } outputIndexes.set(reference, outputIndex.index); } const branchTargets = []; for (const branch of branches) { const targetColumns = []; for (const [reference, outputIndex] of outputIndexes.entries()) { const targetColumn = this.resolveTargetColumnByOutputIndex(root, branch, outputIndex, reference); if ("code" in targetColumn) { return targetColumn; } targetColumns.push(targetColumn); } branchTargets.push(this.resolveDeepestBranchTarget(root, branch, targetColumns)); } return { kind: "union", scopeId, branches: branchTargets }; } resolveTargetColumns(root, query, references) { const resolved = []; for (const reference of references) { const matches = this.collectDirectOutputMatches(root, query, reference.column.name); if (matches.length !== 1) { return { code: "AMBIGUOUS_TARGET_COLUMN", reason: matches.length === 0 ? `Target query does not expose '${reference.column.name}' as a direct output column.` : `Target query exposes multiple '${reference.column.name}' columns.` }; } const value = matches[0].value; if (!(value instanceof ValueComponent_1.ColumnReference)) { return { code: "EXPRESSION_OUTPUT_UNSUPPORTED", reason: `Target output '${reference.column.name}' is an expression, not a direct column reference.` }; } const sourceResolution = this.verifyColumnResolvableInQuery(query, value); if (sourceResolution) { return sourceResolution; } resolved.push({ sourceColumn: reference, targetColumn: value }); } return resolved; } resolveBaseTableJoinOnTarget(contextRoot, currentQuery, binding) { var _a, _b, _c; if (!currentQuery.fromClause || !this.isBaseTableBinding(contextRoot, binding)) { return { code: "NO_SAFE_UPSTREAM_QUERY", reason: "The referenced source is a base table, so there is no upstream query block to move into." }; } if (this.hasOuterJoin(currentQuery.fromClause)) { return { code: "OUTER_JOIN_BOUNDARY", reason: "Predicate crosses an OUTER JOIN boundary; moving it into JOIN ON may change semantics." }; } const join = binding.isPrimary ? (_b = (_a = currentQuery.fromClause.joins) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : null : binding.join; if (join === null || join === void 0 ? void 0 : join.lateral) { return { code: "LATERAL_JOIN_BOUNDARY", reason: "Predicate crosses a LATERAL JOIN boundary; moving it into JOIN ON may change semantics." }; } if (!join || !this.isInnerJoin(join)) { return { code: "NO_SAFE_JOIN_ON_TARGET", reason: "Base-table predicates are moved only into INNER JOIN ON clauses in the safe-only implementation." }; } if (!(join.condition instanceof Clause_1.JoinOnClause)) { return { code: "NO_SAFE_JOIN_ON_TARGET", reason: "Base-table predicates are moved only into existing JOIN ON clauses, not USING or conditionless joins." }; } return { kind: "join_on", join, scopeId: `join_on:${(_c = join.source.getAliasName()) !== null && _c !== void 0 ? _c : "unknown"}`, reason: binding.isPrimary ? "Predicate references the primary source of an INNER JOIN; it is moved into the first JOIN ON clause." : "Predicate references the joined source of an INNER JOIN; it is moved into that JOIN ON clause." }; } appendJoinOnPredicate(join, expression, options) { if (!(join.condition instanceof Clause_1.JoinOnClause)) { return; } join.condition.condition = new ValueComponent_1.BinaryExpression(join.condition.condition, "and", (0, PredicateExpressionUtils_1.cloneValueComponent)(expression, options)); join.condition.condition = (0, TopLevelAndConditionDeduper_1.dedupeTopLevelAndConditions)(join.condition.condition, options); } resolveDeepestBranchTarget(contextRoot, query, targetColumns, visited = new Set()) { if (visited.has(query) || targetColumns.length === 0) { return { query, targetColumns: [...targetColumns] }; } const nextVisited = new Set(visited); nextVisited.add(query); const bindings = []; for (const item of targetColumns) { const binding = this.resolveSourceBinding(contextRoot, query, item.targetColumn); if ("code" in binding) { return { query, targetColumns: [...targetColumns] }; } if (!bindings.some(existing => existing.source === binding.source)) { bindings.push(binding); } } if (bindings.length !== 1) { return { query, targetColumns: [...targetColumns] }; } const binding = bindings[0]; const nullableSide = query.fromClause ? this.findNullableSideBoundary(query.fromClause, binding) : null; if (nullableSide) { return { query, targetColumns: [...targetColumns] }; } const upstream = this.resolveUpstreamQuery(contextRoot, binding, targetColumns.map(item => item.targetColumn)); if ("code" in upstream) { return { query, targetColumns: [...targetColumns] }; } if (upstream.kind !== "simple") { return { query, targetColumns: [...targetColumns] }; } const upstreamColumns = this.resolveTargetColumns(contextRoot, upstream.query, targetColumns.map(item => item.targetColumn)); if ("code" in upstreamColumns) { return { query, targetColumns: [...targetColumns] }; } const placement = this.resolveTargetPlacement(contextRoot, upstream.query, upstreamColumns); if ("code" in placement) { return { query, targetColumns: [...targetColumns] }; } const rebasedColumns = upstreamColumns.map((item, index) => ({ sourceColumn: targetColumns[index].sourceColumn, targetColumn: item.targetColumn })); return this.resolveDeepestBranchTarget(contextRoot, upstream.query, rebasedColumns, nextVisited); } verifyColumnResolvableInQuery(query, column) { if (!query.fromClause) { return { code: "NO_TARGET_FROM_CLAUSE", reason: "Target query has no FROM clause where the moved column can be resolved." }; } const bindings = this.getSourceBindings(query.fromClause); const namespace = column.getNamespace(); if (namespace) { const matches = bindings.filter(binding => (0, PredicateExpressionUtils_1.identifiersEqual)(binding.alias, namespace)); return matches.length === 1 ? null : { code: "AMBIGUOUS_TARGET_COLUMN", reason: `Target column '${(0, PredicateExpressionUtils_1.columnReferenceText)(column)}' is not uniquely resolvable in the destination query.` }; } if (bindings.length === 1) { return null; } return { code: "AMBIGUOUS_TARGET_COLUMN", reason: `Unqualified target column '${column.column.name}' is ambiguous in a multi-source destination query.` }; } isGroupKeyColumn(query, column) { const groupBy = query.groupByClause; if (!groupBy || groupBy.mode || groupBy.grouping.length === 0) { return false; } return groupBy.grouping.some(grouping => { const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(grouping); return candidate instanceof ValueComponent_1.ColumnReference && this.sameResolvableColumnInQuery(query, candidate, column); }); } sameResolvableColumnInQuery(query, left, right) { if ((0, PredicateExpressionUtils_1.sameColumnReference)(left, right)) { return true; } if (!(0, PredicateExpressionUtils_1.identifiersEqual)(left.column.name, right.column.name)) { return false; } const leftSource = this.resolveColumnSourceAlias(query, left); const rightSource = this.resolveColumnSourceAlias(query, right); return leftSource !== null && leftSource === rightSource; } resolveColumnSourceAlias(query, column) { if (!query.fromClause) { return null; } const bindings = this.getSourceBindings(query.fromClause); const namespace = column.getNamespace(); if (namespace) { const matches = bindings.filter(binding => (0, PredicateExpressionUtils_1.identifiersEqual)(binding.alias, namespace)); return matches.length === 1 ? matches[0].alias : null; } return bindings.length === 1 ? bindings[0].alias : null; } rebasePredicate(expression, targetColumns, options) { const cloned = (0, PredicateExpressionUtils_1.cloneValueComponent)(expression, options); const visitSelect = (query, inheritedLocalAliases) => { var _a, _b, _c, _d; if (!(query instanceof SelectQuery_1.SimpleSelectQuery)) { return; } const localAliases = new Set(inheritedLocalAliases); for (const alias of this.collectSourceAliases(query)) { localAliases.add(alias); } query.selectClause.items.forEach(item => visit(item.value, localAliases)); for (const join of (_b = (_a = query.fromClause) === null || _a === void 0 ? void 0 : _a.joins) !== null && _b !== void 0 ? _b : []) { if (join.condition) { visit(join.condition.condition, localAliases); } const source = join.source.datasource; if (source instanceof Clause_1.SubQuerySource) { visitSelect(source.query, localAliases); } } if (query.whereClause) { visit(query.whereClause.condition, localAliases); } if (query.havingClause) { visit(query.havingClause.condition, localAliases); } for (const cte of (_d = (_c = query.withClause) === null || _c === void 0 ? void 0 : _c.tables) !== null && _d !== void 0 ? _d : []) { if (cte.query instanceof SelectQuery_1.SimpleSelectQuery || cte.query instanceof SelectQuery_1.BinarySelectQuery) { visitSelect(cte.query, localAliases); } } }; const visit = (value, localAliases) => { const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(value); if (candidate instanceof ValueComponent_1.ColumnReference) { const namespace = (0, PredicateExpressionUtils_1.normalizeIdentifier)(candidate.getNamespace()); if (namespace && localAliases.has(namespace)) { return; } const target = targetColumns.find(item => (0, PredicateExpressionUtils_1.sameColumnReference)(candidate, item.sourceColumn)); if (target) { candidate.qualifiedName = (0, PredicateExpressionUtils_1.cloneColumnReference)(target.targetColumn).qualifiedName; } return; } if (candidate instanceof ValueComponent_1.BinaryExpression) { visit(candidate.left, localAliases); visit(candidate.right, localAliases); return; } if (candidate instanceof ValueComponent_1.UnaryExpression) { visit(candidate.expression, localAliases); return; } if (candidate instanceof ValueComponent_1.InlineQuery) { visitSelect(candidate.selectQuery, localAliases); return; } if (candidate instanceof ValueComponent_1.FunctionCall) { if (candidate.argument) { visit(candidate.argument, localAliases); } if (candidate.filterCondition) { visit(candidate.filterCondition, localAliases); } return; } if (candidate instanceof ValueComponent_1.CastExpression) { visit(candidate.input, localAliases); return; } if (candidate instanceof ValueComponent_1.CaseExpression) { if (candidate.condition) { visit(candidate.condition, localAliases); } for (const pair of candidate.switchCase.cases) { visit(pair.key, localAliases); visit(pair.value, localAliases); } if (candidate.switchCase.elseValue) { visit(candidate.switchCase.elseValue, localAliases); } return; } if (candidate instanceof ValueComponent_1.BetweenExpression) { visit(candidate.expression, localAliases); visit(candidate.lower, localAliases); visit(candidate.upper, localAliases); return; } if (candidate instanceof ValueComponent_1.JsonPredicateExpression) { visit(candidate.expression, localAliases); return; } if (candidate instanceof ValueComponent_1.ArrayExpression) { visit(candidate.expression, localAliases); return; } if (candidate instanceof ValueComponent_1.ValueList) { candidate.values.forEach(item => visit(item, localAliases)); return; } if (candidate instanceof ValueComponent_1.TupleExpression) { candidate.values.forEach(item => visit(item, localAliases)); return; } if (candidate instanceof ValueComponent_1.TypeValue && candidate.argument) { visit(candidate.argument, localAliases); } }; visit(cloned, new Set()); return cloned; } getSourceBindings(fromClause) { var _a, _b, _c; const bindings = [{ source: fromClause.source, alias: (_a = fromClause.source.getAliasName()) !== null && _a !== void 0 ? _a : "", join: null, joinIndex: -1, isPrimary: true }]; for (let index = 0; index < ((_b = fromClause.joins) !== null && _b !== void 0 ? _b : []).length; index += 1) { const join = fromClause.joins[index]; bindings.push({ source: join.source, alias: (_c = join.source.getAliasName()) !== null && _c !== void 0 ? _c : "", join, joinIndex: index, isPrimary: false }); } return bindings; } isBaseTableBinding(root, binding) { const source = binding.source.datasource; return source instanceof Clause_1.TableSource && !this.findCte(root, source.table.name); } isInnerJoin(join) { const joinType = join.joinType.value.trim().toLowerCase(); return joinType === "join" || joinType === "inner join"; } findNullableSideBoundary(fromClause, binding) { var _a; const joins = (_a = fromClause.joins) !== null && _a !== void 0 ? _a : []; if (binding.isPrimary) { return this.hasLaterJoinThatNullsPriorSources(joins, -1) ? { code: "OUTER_JOIN_NULLABLE_SIDE", reason: "Predicate crosses OUTER JOIN nullable side; moving it may change semantics." } : null; } const join = binding.join; if (!join) { return null; } const joinType = join.joinType.value.toLowerCase(); if (joinType.includes("left")) { return { code: "OUTER_JOIN_NULLABLE_SIDE", reason: "Predicate crosses LEFT JOIN nullable side; moving it may change semantics." }; } if (joinType.includes("full")) { return { code: "OUTER_JOIN_NULLABLE_SIDE", reason: "Predicate crosses FULL JOIN nullable side; moving it may change semantics." }; } if (this.hasLaterJoinThatNullsPriorSources(joins, binding.joinIndex)) { return { code: "OUTER_JOIN_NULLABLE_SIDE", reason: "Predicate crosses later RIGHT/FULL JOIN nullable side; moving it may change semantics." }; } return null; } hasLaterJoinThatNullsPriorSources(joins, sourceJoinIndex) { return joins .slice(sourceJoinIndex + 1) .some(join => { const joinType = join.joinType.value.toLowerCase(); return joinType.includes("right") || joinType.includes("full"); }); } hasOuterJoin(fromClause) { var _a; return ((_a = fromClause.joins) !== null && _a !== void 0 ? _a : []).some(join => { const joinType = join.joinType.value.toLowerCase(); return joinType.includes("left") || joinType.includes("right") || joinType.includes("full") || joinType.includes("outer"); }); } hasDistinctOnBoundary(query) { return query.selectClause.distinct instanceof Clause_1.DistinctOn; } hasOrdinaryDistinct(query) { return query.selectClause.distinct !== null && !this.hasDistinctOnBoundary(query); } getOutputColumnMatchCount(root, binding, columnName) { const target = this.resolveSourceQueryForColumns(root, binding.source); if (!target) { return 0; } if (target instanceof SelectQuery_1.BinarySelectQuery) { const branches = this.collectUnionBranches(target); if ("code" in branches) { return 0; } return this.collectDirectOutputMatches(root, branches[0], columnName).length; } if (!(target instanceof SelectQuery_1.SimpleSelectQuery)) { return 0; } return this.collectDirectOutputMatches(root, target, columnName).length; } collectUnionBranches(query) { const branches = []; const visit = (select) => { var _a; if (select instanceof SelectQuery_1.SimpleSelectQuery) { branches.push(select); return null; } if (!(select instanceof SelectQuery_1.BinarySelectQuery)) { return { code: "UNION_BOUNDARY", reason: "Predicate would need distribution into a non-simple UNION branch, which is unsupported." }; } const operator = select.operator.value.trim().toLowerCase(); if (operator !== "union" && operator !== "union all") { return { code: "UNION_BOUNDARY", reason: `Predicate would need distribution through '${select.operator.value}', which is unsupported.` }; } return (_a = visit(select.left)) !== null && _a !== void 0 ? _a : visit(select.right); }; const skip = visit(query); return skip !== null && skip !== void 0 ? skip : branches; } resolveUnionOutputIndex(root, firstBranch, outputColumnName) { const matches = []; this.collectSelectOutputs(root, firstBranch).forEach((item, index) => { if ((0, PredicateExpressionUtils_1.identifiersEqual)(item.name, outputColumnName)) { matches.push(index); } }); if (matches.length !== 1) { return { code: "AMBIGUOUS_TARGET_COLUMN", reason: matches.length === 0 ? `UNION output does not expose '${outputColumnName}' from its first branch.` : `UNION first branch exposes multiple '${outputColumnName}' columns.` }; } return { index: matches[0] }; } resolveTargetColumnByOutputIndex(root, query, outputIndex, sourceColumn) { const output = this.collectSelectOutputs(root, query)[outputIndex]; if (!output) { return { code: "UNION_COLUMN_MISMATCH", reason: `UNION branch does not expose column '${sourceColumn.column.name}' at output position ${outputIndex + 1}.` }; } else if ((0, PredicateExpressionUtils_1.identifiersEqual)(output.name, sourceColumn.column.name)) { const matches = this.collectDirectOutputMatches(