UNPKG

rawsql-ts

Version:

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

807 lines 36.9 kB
import { SimpleSelectQuery } from "../models/SelectQuery"; import { ArrayExpression, ArrayIndexExpression, ArrayQueryExpression, ArraySliceExpression, BetweenExpression, BinaryExpression, CaseExpression, CastExpression, ColumnReference, FunctionCall, InlineQuery, JsonPredicateExpression, ParenExpression, TupleExpression, TypeValue, UnaryExpression, ValueList } from "../models/ValueComponent"; import { SubQuerySource, TableSource } from "../models/Clause"; import { SelectQueryParser } from "../parsers/SelectQueryParser"; import { ValueParser } from "../parsers/ValueParser"; import { rewriteValueComponentWithColumnResolver } from "../utils/ValueComponentRewriter"; import { ParameterConditionPlacementOptimizer } from "./ParameterConditionPlacementOptimizer"; import { StaticPredicatePlacementOptimizer } from "./StaticPredicatePlacementOptimizer"; import { ConditionDeduplicationOptimizer } from "./ConditionDeduplicationOptimizer"; import { analyzePredicateReachability } from "./PredicateReachabilityAnalyzer"; import { collectSupportedOptionalConditionBranches, pruneOptionalConditionBranches } from "./PruneOptionalConditionBranches"; import { SSSQLFilterBuilder } from "./SSSQLFilterBuilder"; import { formatSqlComponent, hasSqlComponentFormatOverride } from "./SqlComponentFormatter"; import { SqlFormatter } from "./SqlFormatter"; const hasOwnParameter = (parameters, parameterName) => Object.prototype.hasOwnProperty.call(parameters, parameterName); const normalizeIdentifier = (value) => value.trim().toLowerCase(); const unwrapParens = (expression) => { let candidate = expression; while (candidate instanceof ParenExpression) { candidate = candidate.expression; } return candidate; }; const isBinaryOperator = (expression, operator) => { const candidate = unwrapParens(expression); return candidate instanceof BinaryExpression && candidate.operator.value.trim().toLowerCase() === operator; }; const collectTopLevelAndTerms = (expression) => { const candidate = unwrapParens(expression); if (!isBinaryOperator(candidate, "and")) { return [expression]; } return [ ...collectTopLevelAndTerms(candidate.left), ...collectTopLevelAndTerms(candidate.right) ]; }; const uniqueMatchNames = (names) => { const unique = []; for (const name of names) { if (name && !unique.includes(name)) { unique.push(name); } } return unique; }; const getTableSourceTableName = (source) => source.table.name; const getProbeSourceAlias = (source) => { var _a; if (source.aliasExpression) { return source.aliasExpression.table.name; } if (source.datasource instanceof TableSource) { return getTableSourceTableName(source.datasource); } return (_a = source.getAliasName()) !== null && _a !== void 0 ? _a : ""; }; const getProbeSourceMatchNames = (source) => { var _a; if (source.aliasExpression) { return uniqueMatchNames([source.aliasExpression.table.name]); } if (source.datasource instanceof TableSource) { return uniqueMatchNames([ getTableSourceTableName(source.datasource), source.datasource.getSourceName() ]); } return uniqueMatchNames([(_a = source.getAliasName()) !== null && _a !== void 0 ? _a : ""]); }; const collectJoinSourceBindings = (query) => { var _a; if (!query.fromClause) { return []; } const bindings = [{ source: query.fromClause.source, alias: getProbeSourceAlias(query.fromClause.source), matchNames: getProbeSourceMatchNames(query.fromClause.source), join: null, isPrimary: true }]; for (const join of (_a = query.fromClause.joins) !== null && _a !== void 0 ? _a : []) { bindings.push({ source: join.source, alias: getProbeSourceAlias(join.source), matchNames: getProbeSourceMatchNames(join.source), join, isPrimary: false }); } return bindings; }; const isProbeSafeJoin = (join) => { const joinType = join.joinType.value.trim().toLowerCase(); return joinType === "join" || joinType === "inner join" || joinType === "cross join"; }; const collectPredicateReferences = (expression) => { const references = []; let hasNestedQuery = false; const visitSelect = () => { hasNestedQuery = true; }; const visit = (value) => { const candidate = unwrapParens(value); if (candidate instanceof ColumnReference) { references.push(candidate); return; } if (candidate instanceof BinaryExpression) { visit(candidate.left); visit(candidate.right); return; } if (candidate instanceof UnaryExpression) { visit(candidate.expression); return; } if (candidate instanceof InlineQuery) { visitSelect(); return; } if (candidate instanceof ArrayQueryExpression) { visitSelect(); return; } if (candidate instanceof FunctionCall) { if (candidate.argument) { visit(candidate.argument); } if (candidate.filterCondition) { visit(candidate.filterCondition); } return; } if (candidate instanceof CastExpression) { visit(candidate.input); return; } if (candidate instanceof CaseExpression) { if (candidate.condition) { visit(candidate.condition); } for (const pair of candidate.switchCase.cases) { visit(pair.key); visit(pair.value); } if (candidate.switchCase.elseValue) { visit(candidate.switchCase.elseValue); } return; } if (candidate instanceof BetweenExpression) { visit(candidate.expression); visit(candidate.lower); visit(candidate.upper); return; } if (candidate instanceof JsonPredicateExpression) { visit(candidate.expression); return; } if (candidate instanceof ArrayExpression) { visit(candidate.expression); return; } if (candidate instanceof ArrayIndexExpression) { visit(candidate.array); visit(candidate.index); return; } if (candidate instanceof ArraySliceExpression) { visit(candidate.array); if (candidate.startIndex) { visit(candidate.startIndex); } if (candidate.endIndex) { visit(candidate.endIndex); } return; } if (candidate instanceof ValueList) { candidate.values.forEach(visit); return; } if (candidate instanceof TupleExpression) { candidate.values.forEach(visit); return; } if (candidate instanceof TypeValue && candidate.argument) { visit(candidate.argument); } }; visit(expression); return { references, hasNestedQuery }; }; const stripAliasFromPredicate = (expression, sourceMatchNames, options) => { const sourceNames = uniqueMatchNames(sourceMatchNames); return rewriteValueComponentWithColumnResolver(expression, reference => sourceNames.includes(reference.getNamespace()) ? new ColumnReference(null, reference.column.name) : reference); }; const addLowercaseBindingFallback = (lowerBindings, name, binding) => { const normalized = normalizeIdentifier(name); const existing = lowerBindings.get(normalized); if (existing === undefined) { lowerBindings.set(normalized, binding); return; } if (existing !== binding) { lowerBindings.set(normalized, null); } }; const resolveProbeBinding = (exactBindings, lowerBindings, namespace) => { var _a; const exact = exactBindings.get(namespace); if (exact) { return exact; } return (_a = lowerBindings.get(normalizeIdentifier(namespace))) !== null && _a !== void 0 ? _a : null; }; const formatTableSourceForProbe = (source, options) => { return new SqlFormatter(Object.assign({ exportComment: true }, options.formatOptions)).format(source).formattedSql; }; const makeSkippedProbe = (expression, code, reason, options, binding) => { const source = (binding === null || binding === void 0 ? void 0 : binding.source.datasource) instanceof TableSource ? binding.source.datasource.getSourceName() : undefined; return { kind: "source_filter_probe_skipped", source, sourceAlias: (binding === null || binding === void 0 ? void 0 : binding.alias) || undefined, predicate: formatSqlComponent(expression, options), code, reason }; }; const collectConditionOptimizationDiagnostics = (query, options) => { var _a; const probes = []; const skippedProbes = []; if (!(query instanceof SimpleSelectQuery) || !((_a = query.fromClause) === null || _a === void 0 ? void 0 : _a.joins) || !query.whereClause) { return { probes, skippedProbes }; } const bindings = collectJoinSourceBindings(query); const bindingsByExactName = new Map(); const bindingsByLowerName = new Map(); for (const binding of bindings) { for (const matchName of binding.matchNames) { bindingsByExactName.set(matchName, binding); addLowercaseBindingFallback(bindingsByLowerName, matchName, binding); } } for (const term of collectTopLevelAndTerms(query.whereClause.condition)) { const analysis = collectPredicateReferences(term); if (analysis.hasNestedQuery) { skippedProbes.push(makeSkippedProbe(term, "NESTED_QUERY_UNSUPPORTED", "Probe metadata skips predicates containing nested queries because source-local execution cannot be represented safely.", options)); continue; } if (analysis.references.length === 0) { continue; } const namespaces = Array.from(new Set(analysis.references.map(reference => reference.getNamespace()))); if (namespaces.some(namespace => namespace === "")) { skippedProbes.push(makeSkippedProbe(term, "UNQUALIFIED_COLUMN_REFERENCE", "Probe metadata requires qualified column references so the source table is unambiguous.", options)); continue; } if (namespaces.length !== 1) { skippedProbes.push(makeSkippedProbe(term, "MULTI_SOURCE_PREDICATE", "Probe metadata only suggests source filters when the predicate references one joined table.", options)); continue; } const binding = resolveProbeBinding(bindingsByExactName, bindingsByLowerName, namespaces[0]); if (!binding) { skippedProbes.push(makeSkippedProbe(term, "UNKNOWN_SOURCE_ALIAS", `Probe metadata could not match '${namespaces[0]}' to a source in the root FROM clause.`, options)); continue; } if (binding.isPrimary || !binding.join) { continue; } if (binding.join.lateral) { skippedProbes.push(makeSkippedProbe(term, "LATERAL_SOURCE_UNSUPPORTED", "Probe metadata skips lateral join sources because their input can depend on earlier sources.", options, binding)); continue; } if (!isProbeSafeJoin(binding.join)) { skippedProbes.push(makeSkippedProbe(term, "JOIN_TYPE_UNSUPPORTED", `Probe metadata skips '${binding.join.joinType.value}' sources because probing the nullable side can mislead debugging.`, options, binding)); continue; } if (binding.source.datasource instanceof SubQuerySource) { skippedProbes.push(makeSkippedProbe(term, "DERIVED_SOURCE_UNSUPPORTED", "Probe metadata skips derived table sources until their input query can be represented without implying a rewrite.", options, binding)); continue; } if (!(binding.source.datasource instanceof TableSource)) { skippedProbes.push(makeSkippedProbe(term, "SOURCE_KIND_UNSUPPORTED", "Probe metadata currently supports only base table join sources.", options, binding)); continue; } const source = binding.source.datasource.getSourceName(); const predicate = formatSqlComponent(term, options); const sourcePredicate = stripAliasFromPredicate(term, binding.matchNames, options); probes.push({ kind: "source_filter_probe", source, sourceAlias: binding.alias, predicate, suggestedSql: `select * from ${formatTableSourceForProbe(binding.source.datasource, options)} where ${formatSqlComponent(sourcePredicate, options)}`, reason: "predicate references only this joined table" }); } return { probes, skippedProbes }; }; const makeReachabilityProbeSuggestedSql = (sourceName, predicate, options) => { if (!predicate) { return `select * from ${sourceName}`; } const sourceLocalPredicate = rewriteValueComponentWithColumnResolver(ValueParser.parse(predicate), reference => new ColumnReference(null, reference.column.name)); return sourceLocalPredicate ? `select * from ${sourceName} where ${formatSqlComponent(sourceLocalPredicate, options)}` : `select * from ${sourceName}`; }; const collectJoinEquivalenceProbeDiagnostics = (reachability, options) => { const probes = []; const skippedProbes = []; for (const predicate of reachability.predicates) { for (const target of predicate.probeTargets) { if (target.relation !== "join_equivalence") { continue; } probes.push(Object.assign(Object.assign({ kind: "source_filter_probe", source: target.target.name, sourceAlias: target.target.name, predicate: target.predicateSql, suggestedSql: makeReachabilityProbeSuggestedSql(target.target.name, target.targetPredicateSql, options), reason: "predicate inferred through JOIN equivalence for debug probing", relation: "join_equivalence" }, (target.targetPredicateSql ? { targetPredicate: target.targetPredicateSql } : {})), { targetScope: target.target })); } for (const blocked of predicate.blocked) { if (blocked.relation !== "join_equivalence") { continue; } const [, ...nameParts] = blocked.scopeId.split(":"); const source = nameParts.join(":") || undefined; skippedProbes.push({ kind: "source_filter_probe_skipped", source, sourceAlias: source, predicate: predicate.predicateSql, code: blocked.code, reason: blocked.reason }); } } return { probes, skippedProbes }; }; const findDebugCte = (query, name) => { var _a, _b; const normalized = normalizeIdentifier(name); const matches = ((_b = (_a = query.withClause) === null || _a === void 0 ? void 0 : _a.tables) !== null && _b !== void 0 ? _b : []) .filter(table => normalizeIdentifier(table.getSourceAliasName()) === normalized); return matches.length === 1 ? matches[0] : null; }; const buildDebugProbeQuery = (baseQuery, reachability, options) => { var _a; if (!(baseQuery instanceof SimpleSelectQuery)) { return null; } const applicableTargets = reachability.predicates.flatMap(predicate => predicate.probeTargets) .filter(target => target.relation === "join_equivalence" && Boolean(target.targetPredicateSql) && target.target.kind === "cte"); if (applicableTargets.length === 0) { return null; } const debugQuery = SelectQueryParser.parse(formatSqlComponent(baseQuery, options)); if (!(debugQuery instanceof SimpleSelectQuery)) { return null; } let applied = false; for (const target of applicableTargets) { const cte = findDebugCte(debugQuery, target.target.name); if (!cte || !(cte.query instanceof SimpleSelectQuery) || !target.targetPredicateSql) { continue; } cte.query.appendWhereRaw(target.targetPredicateSql); applied = true; } if (!applied) { return null; } const dedupePhase = new ConditionDeduplicationOptimizer().optimize(debugQuery, options); return (_a = dedupePhase.query) !== null && _a !== void 0 ? _a : debugQuery; }; const isAbsentOptionalValue = (parameters, parameterName) => parameters[parameterName] === null || parameters[parameterName] === undefined; const parseConditionOptimizationInput = (input, options) => { const warnings = []; const errors = []; const sourceSql = typeof input === "string" ? input : formatSqlComponent(input, options); const formatterGeneratedSource = typeof input !== "string"; if (typeof input !== "string" && options.cloneInput === false) { return { query: input, sql: sourceSql, formatterGeneratedSource: false, warnings, errors }; } if (formatterGeneratedSource) { warnings.push({ phaseKind: "sssql_optional_condition", code: "AST_INPUT_FORMATTED", message: "AST input is cloned through formatter output so the caller-owned query is not mutated." }); } try { return { query: SelectQueryParser.parse(sourceSql), sql: sourceSql, formatterGeneratedSource, warnings, errors }; } catch (error) { errors.push({ phaseKind: "sssql_optional_condition", code: "PARSE_FAILED", message: "SSSQL optional condition optimization could not parse the input SQL.", detail: error instanceof Error ? error.message : String(error) }); return { query: null, sql: sourceSql, formatterGeneratedSource, warnings, errors }; } }; const buildSssqlApplied = (branch, options) => { return { phaseKind: "sssql_optional_condition", kind: "prune_optional_branch", conditionSql: formatSqlComponent(branch.expression, options), parameterName: branch.parameterName, reason: "The optional branch parameter is explicitly absent, so the safe-only SSSQL phase pruned the branch." }; }; const buildSssqlRefreshApplied = (branch, previousConditionSql, options) => { return { phaseKind: "sssql_optional_condition", kind: "refresh_optional_branch", conditionSql: formatSqlComponent(branch.expression, options), previousConditionSql, parameterName: branch.parameterName, reason: "The safe-only SSSQL phase refreshed the optional branch placement before ordinary parameter placement." }; }; const buildSssqlSkipped = (branch, parameters, options) => { const parameterProvided = hasOwnParameter(parameters, branch.parameterName); return { phaseKind: "sssql_optional_condition", kind: "optional_branch_skipped", conditionSql: formatSqlComponent(branch.expression, options), parameterName: branch.parameterName, code: parameterProvided ? "SSSQL_OPTIONAL_PARAMETER_PRESENT" : "SSSQL_OPTIONAL_PARAMETER_NOT_PROVIDED", reason: parameterProvided ? "The optional branch parameter is present, so the branch remains active." : "No optional branch parameter value was provided, so the branch remains unchanged.", skipDisposition: parameterProvided ? "unchanged" : "ignored" }; }; const buildSssqlRefreshSkipped = (branch, code, reason, skipDisposition, options) => { return { phaseKind: "sssql_optional_condition", kind: "optional_branch_skipped", conditionSql: formatSqlComponent(branch.expression, options), parameterName: branch.parameterName, code, reason, skipDisposition }; }; const buildSssqlRefreshFilters = (branches, parameters) => { const filters = {}; for (const branch of branches) { filters[branch.parameterName] = hasOwnParameter(parameters, branch.parameterName) ? parameters[branch.parameterName] : null; } return filters; }; const refreshRemainingSssqlBranches = (query, fallbackSql, parameters, options) => { var _a, _b; const applied = []; const skipped = []; const beforeBranches = collectSupportedOptionalConditionBranches(query); if (beforeBranches.length === 0) { return { query, sql: fallbackSql, applied, skipped, warnings: [], errors: [], formatterGeneratedSource: false }; } const beforeBranchSql = new WeakMap(); const beforeBranchQuery = new WeakMap(); const parameterCounts = new Map(); for (const branch of beforeBranches) { beforeBranchSql.set(branch.expression, formatSqlComponent(branch.expression, options)); beforeBranchQuery.set(branch.expression, branch.query); parameterCounts.set(branch.parameterName, ((_a = parameterCounts.get(branch.parameterName)) !== null && _a !== void 0 ? _a : 0) + 1); } const alreadyReportedExpressions = new WeakSet(); const refreshableBranches = []; for (const branch of beforeBranches) { if (((_b = parameterCounts.get(branch.parameterName)) !== null && _b !== void 0 ? _b : 0) > 1) { skipped.push(buildSssqlRefreshSkipped(branch, "SSSQL_OPTIONAL_REFRESH_DUPLICATE_PARAMETER_UNCHANGED", "SSSQL optional branch refresh left this duplicate parameter branch unchanged so each query scope can keep its existing local predicate.", "unchanged", options)); alreadyReportedExpressions.add(branch.expression); continue; } refreshableBranches.push(branch); } for (const branch of refreshableBranches) { try { new SSSQLFilterBuilder().refresh(query, buildSssqlRefreshFilters([branch], parameters)); } catch (error) { const detail = error instanceof Error ? error.message : String(error); skipped.push(buildSssqlRefreshSkipped(branch, "SSSQL_OPTIONAL_REFRESH_UNSUPPORTED", `SSSQL optional branch refresh was skipped because safe refresh could not be proven: ${detail}`, "blocked", options)); alreadyReportedExpressions.add(branch.expression); } } const afterBranches = collectSupportedOptionalConditionBranches(query); for (const branch of afterBranches) { if (alreadyReportedExpressions.has(branch.expression)) { continue; } const previousSql = beforeBranchSql.get(branch.expression); const previousQuery = beforeBranchQuery.get(branch.expression); if (!previousSql || !previousQuery) { skipped.push(buildSssqlRefreshSkipped(branch, "SSSQL_OPTIONAL_REFRESH_UNSUPPORTED", "SSSQL optional branch refresh left an untracked branch unchanged.", "blocked", options)); continue; } const refreshedSql = formatSqlComponent(branch.expression, options); if (previousQuery !== branch.query || previousSql !== refreshedSql) { applied.push(buildSssqlRefreshApplied(branch, previousSql, options)); continue; } const parameterProvided = hasOwnParameter(parameters, branch.parameterName); skipped.push(buildSssqlRefreshSkipped(branch, parameterProvided ? "SSSQL_OPTIONAL_REFRESH_NOOP" : "SSSQL_OPTIONAL_PARAMETER_NOT_PROVIDED", parameterProvided ? "SSSQL optional branch refresh found no safe placement change to apply." : "No optional branch parameter value was provided, so the branch was not a refresh target.", parameterProvided ? "unchanged" : "ignored", options)); } return { query, sql: applied.length > 0 || hasSqlComponentFormatOverride(options) ? formatSqlComponent(query, options) : fallbackSql, applied, skipped, warnings: [], errors: [], formatterGeneratedSource: false }; }; const refreshRemainingSssqlBranchesFromSql = (sql, parameters, options) => { const parsed = parseConditionOptimizationInput(sql, options); if (!parsed.query) { return { query: null, sql: parsed.sql, applied: [], skipped: [], warnings: parsed.warnings, errors: parsed.errors, formatterGeneratedSource: parsed.formatterGeneratedSource }; } const refreshed = refreshRemainingSssqlBranches(parsed.query, sql, parameters, options); return Object.assign(Object.assign({}, refreshed), { warnings: [...parsed.warnings, ...refreshed.warnings], errors: [...parsed.errors, ...refreshed.errors], formatterGeneratedSource: parsed.formatterGeneratedSource || refreshed.formatterGeneratedSource }); }; const runSssqlOptionalConditionPhase = (input, options) => { var _a; const reuseOwnedModel = options.cloneInput === false && typeof input !== "string"; const parsed = parseConditionOptimizationInput(input, options); const warnings = [...parsed.warnings]; let errors = [...parsed.errors]; const applied = []; const skipped = []; const optionalConditionParameters = (_a = options.optionalConditionParameters) !== null && _a !== void 0 ? _a : {}; if (!parsed.query) { return { query: null, sql: parsed.sql, applied, skipped, warnings, errors, formatterGeneratedSource: parsed.formatterGeneratedSource }; } let currentSql = parsed.sql; let currentQuery = parsed.query; let formatterGeneratedSource = parsed.formatterGeneratedSource; const branches = collectSupportedOptionalConditionBranches(parsed.query); const pruneBranches = branches.filter(branch => hasOwnParameter(optionalConditionParameters, branch.parameterName) && isAbsentOptionalValue(optionalConditionParameters, branch.parameterName)); if (pruneBranches.length > 0) { try { pruneOptionalConditionBranches(parsed.query, optionalConditionParameters); currentSql = formatSqlComponent(parsed.query, options); applied.push(...pruneBranches.map(branch => buildSssqlApplied(branch, options))); } catch (error) { errors = [...errors, { phaseKind: "sssql_optional_condition", code: "SSSQL_PRUNE_FAILED", message: "SSSQL optional condition optimization could not prune optional branches.", detail: error instanceof Error ? error.message : String(error) }]; } } if (errors.length === 0) { const refreshPhase = reuseOwnedModel ? refreshRemainingSssqlBranches(parsed.query, currentSql, optionalConditionParameters, options) : refreshRemainingSssqlBranchesFromSql(currentSql, optionalConditionParameters, options); applied.push(...refreshPhase.applied); skipped.push(...refreshPhase.skipped); warnings.push(...refreshPhase.warnings); errors.push(...refreshPhase.errors); currentSql = refreshPhase.sql; currentQuery = refreshPhase.query; formatterGeneratedSource = formatterGeneratedSource || refreshPhase.formatterGeneratedSource; } else { for (const branch of branches) { if (pruneBranches.includes(branch)) { continue; } skipped.push(buildSssqlSkipped(branch, optionalConditionParameters, options)); } } return { query: errors.length === 0 ? currentQuery : null, sql: errors.length === 0 ? currentSql : parsed.sql, applied, skipped, warnings, errors, formatterGeneratedSource }; }; const mapParameterWarnings = (warnings) => warnings.map(warning => (Object.assign({ phaseKind: "parameter_condition_placement" }, warning))); const mapParameterErrors = (errors) => errors.map(error => (Object.assign({ phaseKind: "parameter_condition_placement" }, error))); const mapParameterApplied = (applied) => applied.map(item => (Object.assign({ phaseKind: "parameter_condition_placement" }, item))); const mapParameterSkipped = (skipped) => skipped.map(item => (Object.assign({ phaseKind: "parameter_condition_placement", skipDisposition: "blocked" }, item))); const mapStaticWarnings = (warnings) => warnings.map(warning => (Object.assign({ phaseKind: "static_predicate_placement" }, warning))); const mapStaticErrors = (errors) => errors.map(error => (Object.assign({ phaseKind: "static_predicate_placement" }, error))); const mapStaticApplied = (applied) => applied.map(item => (Object.assign({ phaseKind: "static_predicate_placement" }, item))); const mapStaticSkipped = (skipped) => skipped.map(item => (Object.assign({ phaseKind: "static_predicate_placement", skipDisposition: "blocked" }, item))); const mapConditionDeduplicationApplied = (applied) => applied.map(item => (Object.assign({ phaseKind: "condition_deduplication" }, item))); const makePhaseSummary = (kind, counts) => ({ kind, appliedCount: counts.appliedCount, skippedCount: counts.skippedCount, warningCount: counts.warningCount, errorCount: counts.errorCount }); const getAppliedConditionSql = (applied) => { if ("predicateSql" in applied) { return applied.predicateSql; } if ("conditionSql" in applied) { return applied.conditionSql; } return undefined; }; const normalizeDiagnosticPredicate = (predicate, options) => { try { return formatSqlComponent(ValueParser.parse(predicate), options) .replace(/\s+/g, " ") .trim() .toLowerCase(); } catch (_a) { return predicate .replace(/\s+/g, " ") .trim() .toLowerCase(); } }; const filterSkippedProbesAlreadyApplied = (skippedProbes, applied, options) => { const appliedPredicates = new Set(applied .map(item => getAppliedConditionSql(item)) .filter((predicate) => Boolean(predicate)) .map(predicate => normalizeDiagnosticPredicate(predicate, options))); if (appliedPredicates.size === 0) { return [...skippedProbes]; } return skippedProbes.filter(probe => probe.code !== "NESTED_QUERY_UNSUPPORTED" || !appliedPredicates.has(normalizeDiagnosticPredicate(probe.predicate, options))); }; const runConditionOptimization = (input, options, defaultDryRun) => { var _a, _b, _c; const dryRun = (_a = options.dryRun) !== null && _a !== void 0 ? _a : defaultDryRun; const reuseOwnedModel = options.cloneInput === false && typeof input !== "string"; // Run semantic SSSQL handling before generic placement phases so optional // branches remain owned by SSSQL even if later phases grow broader support. const sssqlPhase = runSssqlOptionalConditionPhase(input, Object.assign(Object.assign({}, options), { dryRun })); const sourceDiagnostics = collectConditionOptimizationDiagnostics(sssqlPhase.query, options); const reachability = sssqlPhase.query ? analyzePredicateReachability(sssqlPhase.query, Object.assign(Object.assign({}, options), { cloneInput: false })) : null; const reachabilityDiagnostics = reachability ? collectJoinEquivalenceProbeDiagnostics(reachability, options) : { probes: [], skippedProbes: [] }; const parameterOptimizer = new ParameterConditionPlacementOptimizer(); const parameterInput = reuseOwnedModel ? (_b = sssqlPhase.query) !== null && _b !== void 0 ? _b : sssqlPhase.sql : sssqlPhase.sql; const parameterOptions = Object.assign(Object.assign({}, options), { dryRun, cloneInput: reuseOwnedModel && sssqlPhase.query ? false : options.cloneInput }); const parameterPhase = dryRun ? parameterOptimizer.plan(parameterInput, parameterOptions) : parameterOptimizer.optimize(parameterInput, parameterOptions); const parameterWarnings = mapParameterWarnings(parameterPhase.warnings); const parameterErrors = mapParameterErrors(parameterPhase.errors); const parameterApplied = mapParameterApplied(parameterPhase.applied); const parameterSkipped = mapParameterSkipped(parameterPhase.skipped); const staticOptimizer = new StaticPredicatePlacementOptimizer(); const staticInput = reuseOwnedModel ? (_c = parameterPhase.query) !== null && _c !== void 0 ? _c : parameterPhase.sql : parameterPhase.sql; const staticOptions = Object.assign(Object.assign({}, options), { dryRun, cloneInput: reuseOwnedModel && parameterPhase.query ? false : options.cloneInput }); const staticPhase = dryRun ? staticOptimizer.plan(staticInput, staticOptions) : staticOptimizer.optimize(staticInput, staticOptions); const staticWarnings = mapStaticWarnings(staticPhase.warnings); const staticErrors = mapStaticErrors(staticPhase.errors); const staticApplied = mapStaticApplied(staticPhase.applied); const staticSkipped = mapStaticSkipped(staticPhase.skipped); const warnings = [...sssqlPhase.warnings, ...parameterWarnings, ...staticWarnings]; const errors = [...sssqlPhase.errors, ...parameterErrors, ...staticErrors]; const dedupePhase = errors.length === 0 ? new ConditionDeduplicationOptimizer().optimize(staticPhase.query, options) : { query: staticPhase.query, applied: [] }; const dedupeApplied = mapConditionDeduplicationApplied(dedupePhase.applied); const finalSql = dedupePhase.applied.length > 0 && dedupePhase.query ? formatSqlComponent(dedupePhase.query, options) : staticPhase.sql; const applied = [...sssqlPhase.applied, ...parameterApplied, ...staticApplied, ...dedupeApplied]; const debugQuery = reachability ? buildDebugProbeQuery(dedupePhase.query, reachability, options) : null; const debugSql = debugQuery ? formatSqlComponent(debugQuery, options) : undefined; const diagnostics = Object.assign({ probes: [...sourceDiagnostics.probes, ...reachabilityDiagnostics.probes], skippedProbes: filterSkippedProbesAlreadyApplied([...sourceDiagnostics.skippedProbes, ...reachabilityDiagnostics.skippedProbes], applied, options) }, (debugSql ? { debugSql, debugQuery } : {})); return { ok: errors.length === 0, sql: finalSql, query: dedupePhase.query, phases: [ makePhaseSummary("sssql_optional_condition", { appliedCount: sssqlPhase.applied.length, skippedCount: sssqlPhase.skipped.length, warningCount: sssqlPhase.warnings.length, errorCount: sssqlPhase.errors.length }), makePhaseSummary("parameter_condition_placement", { appliedCount: parameterApplied.length, skippedCount: parameterSkipped.length, warningCount: parameterWarnings.length, errorCount: parameterErrors.length }), makePhaseSummary("static_predicate_placement", { appliedCount: staticApplied.length, skippedCount: staticSkipped.length, warningCount: staticWarnings.length, errorCount: staticErrors.length }), makePhaseSummary("condition_deduplication", { appliedCount: dedupeApplied.length, skippedCount: 0, warningCount: 0, errorCount: 0 }) ], applied, skipped: [...sssqlPhase.skipped, ...parameterSkipped, ...staticSkipped], warnings, errors, safety: { mode: "safe_only", unsafeRewriteApplied: false, dryRun, formatterGeneratedSource: sssqlPhase.formatterGeneratedSource || parameterPhase.safety.formatterGeneratedSource || staticPhase.safety.formatterGeneratedSource }, diagnostics }; }; export const planConditionOptimization = (input, options = {}) => { return runConditionOptimization(input, options, true); }; export const optimizeConditions = (input, options = {}) => { return runConditionOptimization(input, options, false); }; //# sourceMappingURL=ConditionOptimization.js.map