rawsql-ts
Version:
High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
868 lines • 38.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.optimizeConditions = exports.planConditionOptimization = void 0;
const SelectQuery_1 = require("../models/SelectQuery");
const ValueComponent_1 = require("../models/ValueComponent");
const Clause_1 = require("../models/Clause");
const SelectQueryParser_1 = require("../parsers/SelectQueryParser");
const ValueParser_1 = require("../parsers/ValueParser");
const ValueComponentRewriter_1 = require("../utils/ValueComponentRewriter");
const ParameterConditionPlacementOptimizer_1 = require("./ParameterConditionPlacementOptimizer");
const StaticPredicatePlacementOptimizer_1 = require("./StaticPredicatePlacementOptimizer");
const ConditionDeduplicationOptimizer_1 = require("./ConditionDeduplicationOptimizer");
const PredicateReachabilityAnalyzer_1 = require("./PredicateReachabilityAnalyzer");
const PruneOptionalConditionBranches_1 = require("./PruneOptionalConditionBranches");
const SSSQLFilterBuilder_1 = require("./SSSQLFilterBuilder");
const SqlComponentFormatter_1 = require("./SqlComponentFormatter");
const SqlFormatter_1 = require("./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 ValueComponent_1.ParenExpression) {
candidate = candidate.expression;
}
return candidate;
};
const isBinaryOperator = (expression, operator) => {
const candidate = unwrapParens(expression);
return candidate instanceof ValueComponent_1.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 Clause_1.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 Clause_1.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 ValueComponent_1.ColumnReference) {
references.push(candidate);
return;
}
if (candidate instanceof ValueComponent_1.BinaryExpression) {
visit(candidate.left);
visit(candidate.right);
return;
}
if (candidate instanceof ValueComponent_1.UnaryExpression) {
visit(candidate.expression);
return;
}
if (candidate instanceof ValueComponent_1.InlineQuery) {
visitSelect();
return;
}
if (candidate instanceof ValueComponent_1.ArrayQueryExpression) {
visitSelect();
return;
}
if (candidate instanceof ValueComponent_1.FunctionCall) {
if (candidate.argument) {
visit(candidate.argument);
}
if (candidate.filterCondition) {
visit(candidate.filterCondition);
}
return;
}
if (candidate instanceof ValueComponent_1.CastExpression) {
visit(candidate.input);
return;
}
if (candidate instanceof ValueComponent_1.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 ValueComponent_1.BetweenExpression) {
visit(candidate.expression);
visit(candidate.lower);
visit(candidate.upper);
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.ArrayIndexExpression) {
visit(candidate.array);
visit(candidate.index);
return;
}
if (candidate instanceof ValueComponent_1.ArraySliceExpression) {
visit(candidate.array);
if (candidate.startIndex) {
visit(candidate.startIndex);
}
if (candidate.endIndex) {
visit(candidate.endIndex);
}
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 { references, hasNestedQuery };
};
const stripAliasFromPredicate = (expression, sourceMatchNames, options) => {
const sourceNames = uniqueMatchNames(sourceMatchNames);
return (0, ValueComponentRewriter_1.rewriteValueComponentWithColumnResolver)(expression, reference => sourceNames.includes(reference.getNamespace())
? new ValueComponent_1.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_1.SqlFormatter({ 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 Clause_1.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: (0, SqlComponentFormatter_1.formatSqlComponent)(expression, options),
code,
reason
};
};
const collectConditionOptimizationDiagnostics = (query, options) => {
var _a;
const probes = [];
const skippedProbes = [];
if (!(query instanceof SelectQuery_1.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 Clause_1.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 Clause_1.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 = (0, SqlComponentFormatter_1.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 ${(0, SqlComponentFormatter_1.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 = (0, ValueComponentRewriter_1.rewriteValueComponentWithColumnResolver)(ValueParser_1.ValueParser.parse(predicate), reference => new ValueComponent_1.ColumnReference(null, reference.column.name));
return sourceLocalPredicate
? `select * from ${sourceName} where ${(0, SqlComponentFormatter_1.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({
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 SelectQuery_1.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_1.SelectQueryParser.parse((0, SqlComponentFormatter_1.formatSqlComponent)(baseQuery, options));
if (!(debugQuery instanceof SelectQuery_1.SimpleSelectQuery)) {
return null;
}
let applied = false;
for (const target of applicableTargets) {
const cte = findDebugCte(debugQuery, target.target.name);
if (!cte || !(cte.query instanceof SelectQuery_1.SimpleSelectQuery) || !target.targetPredicateSql) {
continue;
}
cte.query.appendWhereRaw(target.targetPredicateSql);
applied = true;
}
if (!applied) {
return null;
}
const dedupePhase = new ConditionDeduplicationOptimizer_1.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 : (0, SqlComponentFormatter_1.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_1.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: (0, SqlComponentFormatter_1.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: (0, SqlComponentFormatter_1.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: (0, SqlComponentFormatter_1.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: (0, SqlComponentFormatter_1.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 = (0, PruneOptionalConditionBranches_1.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, (0, SqlComponentFormatter_1.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_1.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 = (0, PruneOptionalConditionBranches_1.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 = (0, SqlComponentFormatter_1.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 || (0, SqlComponentFormatter_1.hasSqlComponentFormatOverride)(options)
? (0, SqlComponentFormatter_1.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 {
...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 = (0, PruneOptionalConditionBranches_1.collectSupportedOptionalConditionBranches)(parsed.query);
const pruneBranches = branches.filter(branch => hasOwnParameter(optionalConditionParameters, branch.parameterName)
&& isAbsentOptionalValue(optionalConditionParameters, branch.parameterName));
if (pruneBranches.length > 0) {
try {
(0, PruneOptionalConditionBranches_1.pruneOptionalConditionBranches)(parsed.query, optionalConditionParameters);
currentSql = (0, SqlComponentFormatter_1.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 => ({
phaseKind: "parameter_condition_placement",
...warning
}));
const mapParameterErrors = (errors) => errors.map(error => ({
phaseKind: "parameter_condition_placement",
...error
}));
const mapParameterApplied = (applied) => applied.map(item => ({
phaseKind: "parameter_condition_placement",
...item
}));
const mapParameterSkipped = (skipped) => skipped.map(item => ({
phaseKind: "parameter_condition_placement",
skipDisposition: "blocked",
...item
}));
const mapStaticWarnings = (warnings) => warnings.map(warning => ({
phaseKind: "static_predicate_placement",
...warning
}));
const mapStaticErrors = (errors) => errors.map(error => ({
phaseKind: "static_predicate_placement",
...error
}));
const mapStaticApplied = (applied) => applied.map(item => ({
phaseKind: "static_predicate_placement",
...item
}));
const mapStaticSkipped = (skipped) => skipped.map(item => ({
phaseKind: "static_predicate_placement",
skipDisposition: "blocked",
...item
}));
const mapConditionDeduplicationApplied = (applied) => applied.map(item => ({
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 (0, SqlComponentFormatter_1.formatSqlComponent)(ValueParser_1.ValueParser.parse(predicate), options)
.replace(/\s+/g, " ")
.trim()
.toLowerCase();
}
catch {
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, { ...options, dryRun });
const sourceDiagnostics = collectConditionOptimizationDiagnostics(sssqlPhase.query, options);
const reachability = sssqlPhase.query
? (0, PredicateReachabilityAnalyzer_1.analyzePredicateReachability)(sssqlPhase.query, { ...options, cloneInput: false })
: null;
const reachabilityDiagnostics = reachability
? collectJoinEquivalenceProbeDiagnostics(reachability, options)
: { probes: [], skippedProbes: [] };
const parameterOptimizer = new ParameterConditionPlacementOptimizer_1.ParameterConditionPlacementOptimizer();
const parameterInput = reuseOwnedModel
? (_b = sssqlPhase.query) !== null && _b !== void 0 ? _b : sssqlPhase.sql
: sssqlPhase.sql;
const parameterOptions = {
...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_1.StaticPredicatePlacementOptimizer();
const staticInput = reuseOwnedModel
? (_c = parameterPhase.query) !== null && _c !== void 0 ? _c : parameterPhase.sql
: parameterPhase.sql;
const staticOptions = {
...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_1.ConditionDeduplicationOptimizer().optimize(staticPhase.query, options)
: { query: staticPhase.query, applied: [] };
const dedupeApplied = mapConditionDeduplicationApplied(dedupePhase.applied);
const finalSql = dedupePhase.applied.length > 0 && dedupePhase.query
? (0, SqlComponentFormatter_1.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 ? (0, SqlComponentFormatter_1.formatSqlComponent)(debugQuery, options) : undefined;
const diagnostics = {
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
};
};
const planConditionOptimization = (input, options = {}) => {
return runConditionOptimization(input, options, true);
};
exports.planConditionOptimization = planConditionOptimization;
const optimizeConditions = (input, options = {}) => {
return runConditionOptimization(input, options, false);
};
exports.optimizeConditions = optimizeConditions;
//# sourceMappingURL=ConditionOptimization.js.map