rawsql-ts 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.min.js +2 -2
- package/dist/esm/index.min.js.map +4 -4
- package/dist/esm/transformers/ConditionDeduplicationOptimizer.d.ts +22 -0
- package/dist/esm/transformers/ConditionDeduplicationOptimizer.js +198 -0
- package/dist/esm/transformers/ConditionDeduplicationOptimizer.js.map +1 -0
- package/dist/esm/transformers/ConditionOptimization.d.ts +31 -1
- package/dist/esm/transformers/ConditionOptimization.js +321 -4
- package/dist/esm/transformers/ConditionOptimization.js.map +1 -1
- package/dist/esm/transformers/ParameterConditionPlacementOptimizer.d.ts +13 -0
- package/dist/esm/transformers/ParameterConditionPlacementOptimizer.js +238 -87
- package/dist/esm/transformers/ParameterConditionPlacementOptimizer.js.map +1 -1
- package/dist/esm/transformers/PredicateExpressionUtils.d.ts +16 -0
- package/dist/esm/transformers/PredicateExpressionUtils.js +74 -0
- package/dist/esm/transformers/PredicateExpressionUtils.js.map +1 -0
- package/dist/esm/transformers/PredicateReachabilityAnalyzer.d.ts +88 -0
- package/dist/esm/transformers/PredicateReachabilityAnalyzer.js +495 -0
- package/dist/esm/transformers/PredicateReachabilityAnalyzer.js.map +1 -0
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.d.ts +14 -0
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.js +344 -157
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.js.map +1 -1
- package/dist/esm/transformers/TopLevelAndConditionDeduper.d.ts +10 -0
- package/dist/esm/transformers/TopLevelAndConditionDeduper.js +66 -0
- package/dist/esm/transformers/TopLevelAndConditionDeduper.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +2 -2
- package/dist/index.min.js.map +4 -4
- package/dist/src/index.d.ts +1 -0
- package/dist/src/transformers/ConditionDeduplicationOptimizer.d.ts +22 -0
- package/dist/src/transformers/ConditionOptimization.d.ts +31 -1
- package/dist/src/transformers/ParameterConditionPlacementOptimizer.d.ts +13 -0
- package/dist/src/transformers/PredicateExpressionUtils.d.ts +16 -0
- package/dist/src/transformers/PredicateReachabilityAnalyzer.d.ts +88 -0
- package/dist/src/transformers/StaticPredicatePlacementOptimizer.d.ts +14 -0
- package/dist/src/transformers/TopLevelAndConditionDeduper.d.ts +10 -0
- package/dist/transformers/ConditionDeduplicationOptimizer.js +202 -0
- package/dist/transformers/ConditionDeduplicationOptimizer.js.map +1 -0
- package/dist/transformers/ConditionOptimization.js +324 -4
- package/dist/transformers/ConditionOptimization.js.map +1 -1
- package/dist/transformers/ParameterConditionPlacementOptimizer.js +261 -110
- package/dist/transformers/ParameterConditionPlacementOptimizer.js.map +1 -1
- package/dist/transformers/PredicateExpressionUtils.js +88 -0
- package/dist/transformers/PredicateExpressionUtils.js.map +1 -0
- package/dist/transformers/PredicateReachabilityAnalyzer.js +508 -0
- package/dist/transformers/PredicateReachabilityAnalyzer.js.map +1 -0
- package/dist/transformers/StaticPredicatePlacementOptimizer.js +366 -179
- package/dist/transformers/StaticPredicatePlacementOptimizer.js.map +1 -1
- package/dist/transformers/TopLevelAndConditionDeduper.js +72 -0
- package/dist/transformers/TopLevelAndConditionDeduper.js.map +1 -0
- package/dist/tsconfig.browser.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -5,71 +5,12 @@ const Clause_1 = require("../models/Clause");
|
|
|
5
5
|
const SelectQuery_1 = require("../models/SelectQuery");
|
|
6
6
|
const ValueComponent_1 = require("../models/ValueComponent");
|
|
7
7
|
const SelectQueryParser_1 = require("../parsers/SelectQueryParser");
|
|
8
|
-
const ValueParser_1 = require("../parsers/ValueParser");
|
|
9
8
|
const SqlComponentFormatter_1 = require("./SqlComponentFormatter");
|
|
10
9
|
const SelectOutputCollector_1 = require("./SelectOutputCollector");
|
|
10
|
+
const TopLevelAndConditionDeduper_1 = require("./TopLevelAndConditionDeduper");
|
|
11
|
+
const PredicateExpressionUtils_1 = require("./PredicateExpressionUtils");
|
|
11
12
|
const SUPPORTED_OPERATORS = new Set(["=", "<>", "!=", "<", "<=", ">", ">=", "like", "ilike", "in"]);
|
|
12
13
|
const VOLATILE_OR_UNSUPPORTED_FUNCTION_REASON = "Condition contains a function call; volatile and expression predicates are not moved in the safe-only implementation.";
|
|
13
|
-
const normalizeIdentifier = (value) => value.trim().toLowerCase();
|
|
14
|
-
const unwrapParens = (expression) => {
|
|
15
|
-
let candidate = expression;
|
|
16
|
-
while (candidate instanceof ValueComponent_1.ParenExpression) {
|
|
17
|
-
candidate = candidate.expression;
|
|
18
|
-
}
|
|
19
|
-
return candidate;
|
|
20
|
-
};
|
|
21
|
-
const isBinaryOperator = (expression, operator) => {
|
|
22
|
-
const candidate = unwrapParens(expression);
|
|
23
|
-
return candidate instanceof ValueComponent_1.BinaryExpression
|
|
24
|
-
&& candidate.operator.value.trim().toLowerCase() === operator;
|
|
25
|
-
};
|
|
26
|
-
const collectTopLevelAndTerms = (expression) => {
|
|
27
|
-
const candidate = unwrapParens(expression);
|
|
28
|
-
if (!isBinaryOperator(candidate, "and")) {
|
|
29
|
-
return [expression];
|
|
30
|
-
}
|
|
31
|
-
return [
|
|
32
|
-
...collectTopLevelAndTerms(candidate.left),
|
|
33
|
-
...collectTopLevelAndTerms(candidate.right)
|
|
34
|
-
];
|
|
35
|
-
};
|
|
36
|
-
const rebuildWhereWithoutTerms = (query, termsToRemove) => {
|
|
37
|
-
if (!query.whereClause || termsToRemove.size === 0) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const remaining = collectTopLevelAndTerms(query.whereClause.condition)
|
|
41
|
-
.filter(term => !termsToRemove.has(term));
|
|
42
|
-
if (remaining.length === 0) {
|
|
43
|
-
query.whereClause = null;
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
let rebuilt = remaining[0];
|
|
47
|
-
for (let index = 1; index < remaining.length; index += 1) {
|
|
48
|
-
rebuilt = new ValueComponent_1.BinaryExpression(rebuilt, "and", remaining[index]);
|
|
49
|
-
}
|
|
50
|
-
query.whereClause = new Clause_1.WhereClause(rebuilt);
|
|
51
|
-
};
|
|
52
|
-
const cloneValueComponent = (expression, options) => {
|
|
53
|
-
return ValueParser_1.ValueParser.parse((0, SqlComponentFormatter_1.formatSqlComponent)(expression, options));
|
|
54
|
-
};
|
|
55
|
-
const cloneColumnReference = (reference) => {
|
|
56
|
-
var _a, _b;
|
|
57
|
-
const namespaces = (_b = (_a = reference.namespaces) === null || _a === void 0 ? void 0 : _a.map(namespace => namespace.name)) !== null && _b !== void 0 ? _b : null;
|
|
58
|
-
return new ValueComponent_1.ColumnReference(namespaces, reference.column.name);
|
|
59
|
-
};
|
|
60
|
-
const columnReferenceText = (reference) => {
|
|
61
|
-
const namespace = reference.getNamespace();
|
|
62
|
-
return namespace ? `${namespace}.${reference.column.name}` : reference.column.name;
|
|
63
|
-
};
|
|
64
|
-
const sameColumnReference = (left, right) => {
|
|
65
|
-
return normalizeIdentifier(left.column.name) === normalizeIdentifier(right.column.name)
|
|
66
|
-
&& normalizeIdentifier(left.getNamespace()) === normalizeIdentifier(right.getNamespace());
|
|
67
|
-
};
|
|
68
|
-
const appendUnique = (items, value) => {
|
|
69
|
-
if (!items.includes(value)) {
|
|
70
|
-
items.push(value);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
14
|
class ParameterConditionPlacementOptimizer {
|
|
74
15
|
plan(input, options = {}) {
|
|
75
16
|
var _a, _b, _c;
|
|
@@ -108,7 +49,7 @@ class ParameterConditionPlacementOptimizer {
|
|
|
108
49
|
const applied = [];
|
|
109
50
|
const skipped = [];
|
|
110
51
|
const movedTerms = [];
|
|
111
|
-
for (const term of query.whereClause ? collectTopLevelAndTerms(query.whereClause.condition) : []) {
|
|
52
|
+
for (const term of query.whereClause ? (0, PredicateExpressionUtils_1.collectTopLevelAndTerms)(query.whereClause.condition) : []) {
|
|
112
53
|
const candidate = this.analyzeCandidate(term, options);
|
|
113
54
|
if (!candidate) {
|
|
114
55
|
continue;
|
|
@@ -123,7 +64,11 @@ class ParameterConditionPlacementOptimizer {
|
|
|
123
64
|
continue;
|
|
124
65
|
}
|
|
125
66
|
let appliedReason = "";
|
|
126
|
-
if (target.kind === "
|
|
67
|
+
if (target.kind === "join_on") {
|
|
68
|
+
this.appendJoinOnCondition(target.join, candidate.expression, options);
|
|
69
|
+
appliedReason = target.reason;
|
|
70
|
+
}
|
|
71
|
+
else if (target.kind === "simple") {
|
|
127
72
|
const targetColumns = this.resolveTargetColumns(query, target.query, candidate.references);
|
|
128
73
|
if ("code" in targetColumns) {
|
|
129
74
|
skipped.push(this.makeSkipped(term, targetColumns, options));
|
|
@@ -136,6 +81,7 @@ class ParameterConditionPlacementOptimizer {
|
|
|
136
81
|
}
|
|
137
82
|
const movedCondition = this.rebaseCondition(candidate.expression, targetColumns, options);
|
|
138
83
|
target.query.appendWhere(movedCondition);
|
|
84
|
+
(0, TopLevelAndConditionDeduper_1.dedupeWhereTopLevelAndConditions)(target.query, options);
|
|
139
85
|
appliedReason = placement.reason;
|
|
140
86
|
}
|
|
141
87
|
else {
|
|
@@ -156,6 +102,7 @@ class ParameterConditionPlacementOptimizer {
|
|
|
156
102
|
for (const branch of target.branches) {
|
|
157
103
|
const movedCondition = this.rebaseCondition(candidate.expression, branch.targetColumns, options);
|
|
158
104
|
branch.query.appendWhere(movedCondition);
|
|
105
|
+
(0, TopLevelAndConditionDeduper_1.dedupeWhereTopLevelAndConditions)(branch.query, options);
|
|
159
106
|
}
|
|
160
107
|
appliedReason = placements.some(item => /group by/i.test(item.reason))
|
|
161
108
|
? "Condition is distributed to every UNION branch by output column position; grouped branches only receive GROUP BY-key predicates."
|
|
@@ -169,10 +116,10 @@ class ParameterConditionPlacementOptimizer {
|
|
|
169
116
|
toScopeId: target.scopeId,
|
|
170
117
|
reason: appliedReason,
|
|
171
118
|
parameterNames: candidate.parameterNames,
|
|
172
|
-
columnReferences: candidate.references.map(columnReferenceText)
|
|
119
|
+
columnReferences: candidate.references.map(PredicateExpressionUtils_1.columnReferenceText)
|
|
173
120
|
});
|
|
174
121
|
}
|
|
175
|
-
rebuildWhereWithoutTerms(query, new Set(movedTerms));
|
|
122
|
+
(0, PredicateExpressionUtils_1.rebuildWhereWithoutTerms)(query, new Set(movedTerms));
|
|
176
123
|
const sql = applied.length > 0 || (0, SqlComponentFormatter_1.hasSqlComponentFormatOverride)(options)
|
|
177
124
|
? (0, SqlComponentFormatter_1.formatSqlComponent)(query, options)
|
|
178
125
|
: parsed.sql;
|
|
@@ -270,7 +217,7 @@ class ParameterConditionPlacementOptimizer {
|
|
|
270
217
|
if (found) {
|
|
271
218
|
return;
|
|
272
219
|
}
|
|
273
|
-
const candidate = unwrapParens(value);
|
|
220
|
+
const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(value);
|
|
274
221
|
if (candidate instanceof ValueComponent_1.BinaryExpression) {
|
|
275
222
|
visit(candidate.left);
|
|
276
223
|
visit(candidate.right);
|
|
@@ -331,7 +278,7 @@ class ParameterConditionPlacementOptimizer {
|
|
|
331
278
|
findUnsupportedParameterPredicateShape(expression) {
|
|
332
279
|
const visit = (value) => {
|
|
333
280
|
var _a;
|
|
334
|
-
const candidate = unwrapParens(value);
|
|
281
|
+
const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(value);
|
|
335
282
|
if (candidate instanceof ValueComponent_1.BetweenExpression) {
|
|
336
283
|
return null;
|
|
337
284
|
}
|
|
@@ -354,16 +301,22 @@ class ParameterConditionPlacementOptimizer {
|
|
|
354
301
|
}
|
|
355
302
|
return null;
|
|
356
303
|
}
|
|
304
|
+
if (candidate instanceof ValueComponent_1.UnaryExpression) {
|
|
305
|
+
const operator = candidate.operator.value.trim().toLowerCase();
|
|
306
|
+
if (operator === "not") {
|
|
307
|
+
return visit(candidate.expression);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
357
310
|
return {
|
|
358
311
|
code: "UNSUPPORTED_PARAMETER_CONDITION",
|
|
359
|
-
reason: "Only simple binary, BETWEEN, and whole OR/AND parameter predicates are moved in the safe-only implementation."
|
|
312
|
+
reason: "Only simple binary, BETWEEN, and whole OR/AND/NOT parameter predicates are moved in the safe-only implementation."
|
|
360
313
|
};
|
|
361
314
|
};
|
|
362
315
|
return visit(expression);
|
|
363
316
|
}
|
|
364
317
|
isSupportedInPredicate(expression) {
|
|
365
|
-
const left = unwrapParens(expression.left);
|
|
366
|
-
const right = unwrapParens(expression.right);
|
|
318
|
+
const left = (0, PredicateExpressionUtils_1.unwrapParens)(expression.left);
|
|
319
|
+
const right = (0, PredicateExpressionUtils_1.unwrapParens)(expression.right);
|
|
367
320
|
if (!(left instanceof ValueComponent_1.ColumnReference)) {
|
|
368
321
|
return false;
|
|
369
322
|
}
|
|
@@ -374,9 +327,10 @@ class ParameterConditionPlacementOptimizer {
|
|
|
374
327
|
return false;
|
|
375
328
|
}
|
|
376
329
|
return right.values.length > 0
|
|
377
|
-
&& right.values.every(value => unwrapParens(value) instanceof ValueComponent_1.ParameterExpression);
|
|
330
|
+
&& right.values.every(value => (0, PredicateExpressionUtils_1.unwrapParens)(value) instanceof ValueComponent_1.ParameterExpression);
|
|
378
331
|
}
|
|
379
332
|
resolveTarget(root, candidate) {
|
|
333
|
+
var _a;
|
|
380
334
|
const boundary = this.findRootQueryBoundary(root);
|
|
381
335
|
if (boundary) {
|
|
382
336
|
return boundary;
|
|
@@ -404,12 +358,25 @@ class ParameterConditionPlacementOptimizer {
|
|
|
404
358
|
};
|
|
405
359
|
}
|
|
406
360
|
const binding = bindings[0];
|
|
361
|
+
if ((_a = binding.join) === null || _a === void 0 ? void 0 : _a.lateral) {
|
|
362
|
+
return {
|
|
363
|
+
code: "LATERAL_JOIN_BOUNDARY",
|
|
364
|
+
reason: "Condition crosses a LATERAL JOIN boundary; moving it may change semantics."
|
|
365
|
+
};
|
|
366
|
+
}
|
|
407
367
|
const nullableSide = this.findNullableSideBoundary(root.fromClause, binding);
|
|
408
368
|
if (nullableSide) {
|
|
409
369
|
return nullableSide;
|
|
410
370
|
}
|
|
411
371
|
const upstream = this.resolveUpstreamQuery(root, binding, candidate.references);
|
|
412
372
|
if ("code" in upstream) {
|
|
373
|
+
if (upstream.code === "NO_SAFE_UPSTREAM_QUERY") {
|
|
374
|
+
const joinOnTarget = this.resolveBaseTableJoinOnTarget(root, binding);
|
|
375
|
+
if (!("code" in joinOnTarget)) {
|
|
376
|
+
return joinOnTarget;
|
|
377
|
+
}
|
|
378
|
+
return joinOnTarget;
|
|
379
|
+
}
|
|
413
380
|
return upstream;
|
|
414
381
|
}
|
|
415
382
|
return upstream;
|
|
@@ -491,10 +458,10 @@ class ParameterConditionPlacementOptimizer {
|
|
|
491
458
|
};
|
|
492
459
|
}
|
|
493
460
|
const bindings = this.getSourceBindings(fromClause);
|
|
494
|
-
const namespace =
|
|
461
|
+
const namespace = column.getNamespace();
|
|
495
462
|
const columnName = column.column.name;
|
|
496
463
|
if (namespace) {
|
|
497
|
-
const matches = bindings.filter(binding =>
|
|
464
|
+
const matches = bindings.filter(binding => (0, PredicateExpressionUtils_1.identifiersEqual)(binding.alias, namespace));
|
|
498
465
|
if (matches.length !== 1) {
|
|
499
466
|
return {
|
|
500
467
|
code: "AMBIGUOUS_COLUMN_SOURCE",
|
|
@@ -502,16 +469,19 @@ class ParameterConditionPlacementOptimizer {
|
|
|
502
469
|
};
|
|
503
470
|
}
|
|
504
471
|
const matchCount = this.getOutputColumnMatchCount(contextRoot, matches[0], columnName);
|
|
472
|
+
if (matchCount === 0 && this.isBaseTableBinding(contextRoot, matches[0])) {
|
|
473
|
+
return matches[0];
|
|
474
|
+
}
|
|
505
475
|
if (matchCount === 0) {
|
|
506
476
|
return {
|
|
507
477
|
code: "COLUMN_NOT_AVAILABLE_UPSTREAM",
|
|
508
|
-
reason: `Column '${columnReferenceText(column)}' is not a direct output of the referenced source.`
|
|
478
|
+
reason: `Column '${(0, PredicateExpressionUtils_1.columnReferenceText)(column)}' is not a direct output of the referenced source.`
|
|
509
479
|
};
|
|
510
480
|
}
|
|
511
481
|
if (matchCount > 1) {
|
|
512
482
|
return {
|
|
513
483
|
code: "AMBIGUOUS_COLUMN_REFERENCE",
|
|
514
|
-
reason: `Column '${columnReferenceText(column)}' resolves to multiple outputs in the referenced source.`
|
|
484
|
+
reason: `Column '${(0, PredicateExpressionUtils_1.columnReferenceText)(column)}' resolves to multiple outputs in the referenced source.`
|
|
515
485
|
};
|
|
516
486
|
}
|
|
517
487
|
return matches[0];
|
|
@@ -630,7 +600,7 @@ class ParameterConditionPlacementOptimizer {
|
|
|
630
600
|
resolveTargetColumns(root, query, references) {
|
|
631
601
|
const resolved = [];
|
|
632
602
|
for (const reference of references) {
|
|
633
|
-
const matches = this.
|
|
603
|
+
const matches = this.collectDirectOutputMatches(root, query, reference.column.name);
|
|
634
604
|
if (matches.length !== 1) {
|
|
635
605
|
return {
|
|
636
606
|
code: "AMBIGUOUS_TARGET_COLUMN",
|
|
@@ -657,6 +627,57 @@ class ParameterConditionPlacementOptimizer {
|
|
|
657
627
|
}
|
|
658
628
|
return resolved;
|
|
659
629
|
}
|
|
630
|
+
resolveBaseTableJoinOnTarget(root, binding) {
|
|
631
|
+
var _a, _b, _c;
|
|
632
|
+
if (!root.fromClause || !this.isBaseTableBinding(root, binding)) {
|
|
633
|
+
return {
|
|
634
|
+
code: "NO_SAFE_UPSTREAM_QUERY",
|
|
635
|
+
reason: "The referenced source is a base table, so there is no upstream query block to move into."
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
if (this.hasOuterJoin(root.fromClause)) {
|
|
639
|
+
return {
|
|
640
|
+
code: "OUTER_JOIN_BOUNDARY",
|
|
641
|
+
reason: "Condition crosses an OUTER JOIN boundary; moving it into JOIN ON may change semantics."
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
const join = binding.isPrimary
|
|
645
|
+
? (_b = (_a = root.fromClause.joins) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : null
|
|
646
|
+
: binding.join;
|
|
647
|
+
if (join === null || join === void 0 ? void 0 : join.lateral) {
|
|
648
|
+
return {
|
|
649
|
+
code: "LATERAL_JOIN_BOUNDARY",
|
|
650
|
+
reason: "Condition crosses a LATERAL JOIN boundary; moving it into JOIN ON may change semantics."
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
if (!join || !this.isInnerJoin(join)) {
|
|
654
|
+
return {
|
|
655
|
+
code: "NO_SAFE_JOIN_ON_TARGET",
|
|
656
|
+
reason: "Base-table conditions are moved only into INNER JOIN ON clauses in the safe-only implementation."
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
if (!(join.condition instanceof Clause_1.JoinOnClause)) {
|
|
660
|
+
return {
|
|
661
|
+
code: "NO_SAFE_JOIN_ON_TARGET",
|
|
662
|
+
reason: "Base-table conditions are moved only into existing JOIN ON clauses, not USING or conditionless joins."
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
return {
|
|
666
|
+
kind: "join_on",
|
|
667
|
+
join,
|
|
668
|
+
scopeId: `join_on:${(_c = join.source.getAliasName()) !== null && _c !== void 0 ? _c : "unknown"}`,
|
|
669
|
+
reason: binding.isPrimary
|
|
670
|
+
? "Condition references the primary source of an INNER JOIN; it is moved into the first JOIN ON clause."
|
|
671
|
+
: "Condition references the joined source of an INNER JOIN; it is moved into that JOIN ON clause."
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
appendJoinOnCondition(join, expression, options) {
|
|
675
|
+
if (!(join.condition instanceof Clause_1.JoinOnClause)) {
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
join.condition.condition = new ValueComponent_1.BinaryExpression(join.condition.condition, "and", (0, PredicateExpressionUtils_1.cloneValueComponent)(expression, options));
|
|
679
|
+
join.condition.condition = (0, TopLevelAndConditionDeduper_1.dedupeTopLevelAndConditions)(join.condition.condition, options);
|
|
680
|
+
}
|
|
660
681
|
resolveTargetColumnByOutputIndex(root, query, outputIndex, sourceColumn) {
|
|
661
682
|
const output = this.collectSelectOutputs(root, query)[outputIndex];
|
|
662
683
|
if (!output) {
|
|
@@ -665,6 +686,15 @@ class ParameterConditionPlacementOptimizer {
|
|
|
665
686
|
reason: `UNION branch does not expose column '${sourceColumn.column.name}' at output position ${outputIndex + 1}.`
|
|
666
687
|
};
|
|
667
688
|
}
|
|
689
|
+
else if ((0, PredicateExpressionUtils_1.identifiersEqual)(output.name, sourceColumn.column.name)) {
|
|
690
|
+
const matches = this.collectDirectOutputMatches(root, query, sourceColumn.column.name);
|
|
691
|
+
if (matches.length > 1) {
|
|
692
|
+
return {
|
|
693
|
+
code: "AMBIGUOUS_TARGET_COLUMN",
|
|
694
|
+
reason: `UNION branch exposes multiple '${sourceColumn.column.name}' columns.`
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
}
|
|
668
698
|
if (!(output.value instanceof ValueComponent_1.ColumnReference)) {
|
|
669
699
|
return {
|
|
670
700
|
code: "EXPRESSION_OUTPUT_UNSUPPORTED",
|
|
@@ -707,7 +737,10 @@ class ParameterConditionPlacementOptimizer {
|
|
|
707
737
|
return { query, targetColumns: [...targetColumns] };
|
|
708
738
|
}
|
|
709
739
|
const upstream = this.resolveUpstreamQuery(contextRoot, binding, targetColumns.map(item => item.targetColumn));
|
|
710
|
-
if ("code" in upstream
|
|
740
|
+
if ("code" in upstream) {
|
|
741
|
+
return { query, targetColumns: [...targetColumns] };
|
|
742
|
+
}
|
|
743
|
+
if (upstream.kind !== "simple") {
|
|
711
744
|
return { query, targetColumns: [...targetColumns] };
|
|
712
745
|
}
|
|
713
746
|
const upstreamColumns = this.resolveTargetColumns(contextRoot, upstream.query, targetColumns.map(item => item.targetColumn));
|
|
@@ -732,14 +765,14 @@ class ParameterConditionPlacementOptimizer {
|
|
|
732
765
|
};
|
|
733
766
|
}
|
|
734
767
|
const bindings = this.getSourceBindings(query.fromClause);
|
|
735
|
-
const namespace =
|
|
768
|
+
const namespace = column.getNamespace();
|
|
736
769
|
if (namespace) {
|
|
737
|
-
const matches = bindings.filter(binding =>
|
|
770
|
+
const matches = bindings.filter(binding => (0, PredicateExpressionUtils_1.identifiersEqual)(binding.alias, namespace));
|
|
738
771
|
return matches.length === 1
|
|
739
772
|
? null
|
|
740
773
|
: {
|
|
741
774
|
code: "AMBIGUOUS_TARGET_COLUMN",
|
|
742
|
-
reason: `Target column '${columnReferenceText(column)}' is not uniquely resolvable in the destination query.`
|
|
775
|
+
reason: `Target column '${(0, PredicateExpressionUtils_1.columnReferenceText)(column)}' is not uniquely resolvable in the destination query.`
|
|
743
776
|
};
|
|
744
777
|
}
|
|
745
778
|
if (bindings.length === 1) {
|
|
@@ -756,16 +789,16 @@ class ParameterConditionPlacementOptimizer {
|
|
|
756
789
|
return false;
|
|
757
790
|
}
|
|
758
791
|
return groupBy.grouping.some(grouping => {
|
|
759
|
-
const candidate = unwrapParens(grouping);
|
|
792
|
+
const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(grouping);
|
|
760
793
|
return candidate instanceof ValueComponent_1.ColumnReference
|
|
761
794
|
&& this.sameResolvableColumnInQuery(query, candidate, column);
|
|
762
795
|
});
|
|
763
796
|
}
|
|
764
797
|
sameResolvableColumnInQuery(query, left, right) {
|
|
765
|
-
if (sameColumnReference(left, right)) {
|
|
798
|
+
if ((0, PredicateExpressionUtils_1.sameColumnReference)(left, right)) {
|
|
766
799
|
return true;
|
|
767
800
|
}
|
|
768
|
-
if (
|
|
801
|
+
if (!(0, PredicateExpressionUtils_1.identifiersEqual)(left.column.name, right.column.name)) {
|
|
769
802
|
return false;
|
|
770
803
|
}
|
|
771
804
|
const leftSource = this.resolveColumnSourceAlias(query, left);
|
|
@@ -777,19 +810,19 @@ class ParameterConditionPlacementOptimizer {
|
|
|
777
810
|
return null;
|
|
778
811
|
}
|
|
779
812
|
const bindings = this.getSourceBindings(query.fromClause);
|
|
780
|
-
const namespace =
|
|
813
|
+
const namespace = column.getNamespace();
|
|
781
814
|
if (namespace) {
|
|
782
|
-
const matches = bindings.filter(binding =>
|
|
783
|
-
return matches.length === 1 ?
|
|
815
|
+
const matches = bindings.filter(binding => (0, PredicateExpressionUtils_1.identifiersEqual)(binding.alias, namespace));
|
|
816
|
+
return matches.length === 1 ? matches[0].alias : null;
|
|
784
817
|
}
|
|
785
|
-
return bindings.length === 1 ?
|
|
818
|
+
return bindings.length === 1 ? bindings[0].alias : null;
|
|
786
819
|
}
|
|
787
820
|
rebaseCondition(expression, targetColumns, options) {
|
|
788
|
-
const cloned = cloneValueComponent(expression, options);
|
|
821
|
+
const cloned = (0, PredicateExpressionUtils_1.cloneValueComponent)(expression, options);
|
|
789
822
|
for (const reference of this.collectColumnReferences(cloned)) {
|
|
790
|
-
const target = targetColumns.find(item => sameColumnReference(reference, item.sourceColumn));
|
|
823
|
+
const target = targetColumns.find(item => (0, PredicateExpressionUtils_1.sameColumnReference)(reference, item.sourceColumn));
|
|
791
824
|
if (target) {
|
|
792
|
-
reference.qualifiedName = cloneColumnReference(target.targetColumn).qualifiedName;
|
|
825
|
+
reference.qualifiedName = (0, PredicateExpressionUtils_1.cloneColumnReference)(target.targetColumn).qualifiedName;
|
|
793
826
|
}
|
|
794
827
|
}
|
|
795
828
|
return cloned;
|
|
@@ -800,33 +833,45 @@ class ParameterConditionPlacementOptimizer {
|
|
|
800
833
|
source: fromClause.source,
|
|
801
834
|
alias: (_a = fromClause.source.getAliasName()) !== null && _a !== void 0 ? _a : "",
|
|
802
835
|
join: null,
|
|
836
|
+
joinIndex: -1,
|
|
803
837
|
isPrimary: true
|
|
804
838
|
}];
|
|
805
|
-
for (
|
|
839
|
+
for (let index = 0; index < ((_b = fromClause.joins) !== null && _b !== void 0 ? _b : []).length; index += 1) {
|
|
840
|
+
const join = fromClause.joins[index];
|
|
806
841
|
bindings.push({
|
|
807
842
|
source: join.source,
|
|
808
843
|
alias: (_c = join.source.getAliasName()) !== null && _c !== void 0 ? _c : "",
|
|
809
844
|
join,
|
|
845
|
+
joinIndex: index,
|
|
810
846
|
isPrimary: false
|
|
811
847
|
});
|
|
812
848
|
}
|
|
813
849
|
return bindings;
|
|
814
850
|
}
|
|
851
|
+
isBaseTableBinding(root, binding) {
|
|
852
|
+
const source = binding.source.datasource;
|
|
853
|
+
return source instanceof Clause_1.TableSource && !this.findCte(root, source.table.name);
|
|
854
|
+
}
|
|
855
|
+
isInnerJoin(join) {
|
|
856
|
+
const joinType = join.joinType.value.trim().toLowerCase();
|
|
857
|
+
return joinType === "join" || joinType === "inner join";
|
|
858
|
+
}
|
|
815
859
|
findNullableSideBoundary(fromClause, binding) {
|
|
816
860
|
var _a;
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
return joinType.includes("right") || joinType.includes("full");
|
|
821
|
-
});
|
|
822
|
-
return rightOrFull
|
|
861
|
+
const joins = (_a = fromClause.joins) !== null && _a !== void 0 ? _a : [];
|
|
862
|
+
if (binding.isPrimary) {
|
|
863
|
+
return this.hasLaterJoinThatNullsPriorSources(joins, -1)
|
|
823
864
|
? {
|
|
824
865
|
code: "OUTER_JOIN_NULLABLE_SIDE",
|
|
825
866
|
reason: "Condition crosses OUTER JOIN nullable side; moving it may change semantics."
|
|
826
867
|
}
|
|
827
868
|
: null;
|
|
828
869
|
}
|
|
829
|
-
const
|
|
870
|
+
const join = binding.join;
|
|
871
|
+
if (!join) {
|
|
872
|
+
return null;
|
|
873
|
+
}
|
|
874
|
+
const joinType = join.joinType.value.toLowerCase();
|
|
830
875
|
if (joinType.includes("left")) {
|
|
831
876
|
return {
|
|
832
877
|
code: "OUTER_JOIN_NULLABLE_SIDE",
|
|
@@ -839,8 +884,22 @@ class ParameterConditionPlacementOptimizer {
|
|
|
839
884
|
reason: "Condition crosses FULL JOIN nullable side; moving it may change semantics."
|
|
840
885
|
};
|
|
841
886
|
}
|
|
887
|
+
if (this.hasLaterJoinThatNullsPriorSources(joins, binding.joinIndex)) {
|
|
888
|
+
return {
|
|
889
|
+
code: "OUTER_JOIN_NULLABLE_SIDE",
|
|
890
|
+
reason: "Condition crosses later RIGHT/FULL JOIN nullable side; moving it may change semantics."
|
|
891
|
+
};
|
|
892
|
+
}
|
|
842
893
|
return null;
|
|
843
894
|
}
|
|
895
|
+
hasLaterJoinThatNullsPriorSources(joins, sourceJoinIndex) {
|
|
896
|
+
return joins
|
|
897
|
+
.slice(sourceJoinIndex + 1)
|
|
898
|
+
.some(join => {
|
|
899
|
+
const joinType = join.joinType.value.toLowerCase();
|
|
900
|
+
return joinType.includes("right") || joinType.includes("full");
|
|
901
|
+
});
|
|
902
|
+
}
|
|
844
903
|
hasOuterJoin(fromClause) {
|
|
845
904
|
var _a;
|
|
846
905
|
return ((_a = fromClause.joins) !== null && _a !== void 0 ? _a : []).some(join => {
|
|
@@ -864,12 +923,12 @@ class ParameterConditionPlacementOptimizer {
|
|
|
864
923
|
if ("code" in branches) {
|
|
865
924
|
return 0;
|
|
866
925
|
}
|
|
867
|
-
return this.
|
|
926
|
+
return this.collectDirectOutputMatches(root, branches[0], columnName).length;
|
|
868
927
|
}
|
|
869
928
|
if (!(target instanceof SelectQuery_1.SimpleSelectQuery)) {
|
|
870
929
|
return 0;
|
|
871
930
|
}
|
|
872
|
-
return this.
|
|
931
|
+
return this.collectDirectOutputMatches(root, target, columnName, true).length;
|
|
873
932
|
}
|
|
874
933
|
collectUnionBranches(query) {
|
|
875
934
|
const branches = [];
|
|
@@ -900,7 +959,7 @@ class ParameterConditionPlacementOptimizer {
|
|
|
900
959
|
resolveUnionOutputIndex(root, firstBranch, outputColumnName) {
|
|
901
960
|
const matches = [];
|
|
902
961
|
this.collectSelectOutputs(root, firstBranch).forEach((item, index) => {
|
|
903
|
-
if (
|
|
962
|
+
if ((0, PredicateExpressionUtils_1.identifiersEqual)(item.name, outputColumnName)) {
|
|
904
963
|
matches.push(index);
|
|
905
964
|
}
|
|
906
965
|
});
|
|
@@ -923,6 +982,98 @@ class ParameterConditionPlacementOptimizer {
|
|
|
923
982
|
const collector = new SelectOutputCollector_1.SelectOutputCollector(null, commonTables.length > 0 ? commonTables : null);
|
|
924
983
|
return collector.collect(query);
|
|
925
984
|
}
|
|
985
|
+
collectDirectOutputMatches(root, query, columnName, includeUnprovenPotential = false) {
|
|
986
|
+
const collectedMatches = this.collectSelectOutputs(root, query).filter(item => (0, PredicateExpressionUtils_1.identifiersEqual)(item.name, columnName));
|
|
987
|
+
if (collectedMatches.length > 0) {
|
|
988
|
+
return this.hasExplicitOutputMatch(query, columnName)
|
|
989
|
+
? [...collectedMatches, ...this.inferWildcardOutputMatches(root, query, columnName, true)]
|
|
990
|
+
: collectedMatches;
|
|
991
|
+
}
|
|
992
|
+
return this.inferWildcardOutputMatches(root, query, columnName, includeUnprovenPotential);
|
|
993
|
+
}
|
|
994
|
+
hasExplicitOutputMatch(query, columnName) {
|
|
995
|
+
return query.selectClause.items.some(item => {
|
|
996
|
+
if (item.identifier) {
|
|
997
|
+
return (0, PredicateExpressionUtils_1.identifiersEqual)(item.identifier.name, columnName);
|
|
998
|
+
}
|
|
999
|
+
return item.value instanceof ValueComponent_1.ColumnReference
|
|
1000
|
+
&& item.value.column.name !== "*"
|
|
1001
|
+
&& (0, PredicateExpressionUtils_1.identifiersEqual)(item.value.column.name, columnName);
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
inferWildcardOutputMatches(root, query, columnName, includeUnprovenPotential) {
|
|
1005
|
+
const matches = [];
|
|
1006
|
+
query.selectClause.items.forEach((item, outputIndex) => {
|
|
1007
|
+
if (item.identifier || !(item.value instanceof ValueComponent_1.ColumnReference) || item.value.column.name !== "*") {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
const targetColumn = this.inferWildcardTargetColumn(root, query, item.value, columnName, includeUnprovenPotential);
|
|
1011
|
+
if (!targetColumn) {
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
if (targetColumn === "ambiguous") {
|
|
1015
|
+
matches.push(this.createInferredOutputColumn(columnName, new ValueComponent_1.ColumnReference(null, columnName), outputIndex), this.createInferredOutputColumn(columnName, new ValueComponent_1.ColumnReference(null, columnName), outputIndex));
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
matches.push(this.createInferredOutputColumn(columnName, targetColumn, outputIndex));
|
|
1019
|
+
});
|
|
1020
|
+
return matches;
|
|
1021
|
+
}
|
|
1022
|
+
inferWildcardTargetColumn(root, query, wildcard, columnName, includeUnprovenPotential) {
|
|
1023
|
+
if (!query.fromClause) {
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
const bindings = this.getSourceBindings(query.fromClause);
|
|
1027
|
+
if (wildcard.namespaces === null) {
|
|
1028
|
+
if (bindings.length !== 1) {
|
|
1029
|
+
return "ambiguous";
|
|
1030
|
+
}
|
|
1031
|
+
return this.resolveWildcardColumnFromBinding(root, query, bindings[0], columnName, includeUnprovenPotential);
|
|
1032
|
+
}
|
|
1033
|
+
const namespace = wildcard.getNamespace();
|
|
1034
|
+
const matches = bindings.filter(binding => (0, PredicateExpressionUtils_1.identifiersEqual)(binding.alias, namespace));
|
|
1035
|
+
if (matches.length === 0) {
|
|
1036
|
+
return null;
|
|
1037
|
+
}
|
|
1038
|
+
return matches.length === 1
|
|
1039
|
+
? this.resolveWildcardColumnFromBinding(root, query, matches[0], columnName, includeUnprovenPotential)
|
|
1040
|
+
: "ambiguous";
|
|
1041
|
+
}
|
|
1042
|
+
resolveWildcardColumnFromBinding(root, query, binding, columnName, includeUnprovenPotential) {
|
|
1043
|
+
const matches = this.collectSourceOutputMatches(root, query, binding.source, columnName);
|
|
1044
|
+
if (matches.length > 1) {
|
|
1045
|
+
return "ambiguous";
|
|
1046
|
+
}
|
|
1047
|
+
if (matches.length === 1) {
|
|
1048
|
+
const value = matches[0].value;
|
|
1049
|
+
return value instanceof ValueComponent_1.ColumnReference ? value : null;
|
|
1050
|
+
}
|
|
1051
|
+
return includeUnprovenPotential && binding.source.datasource instanceof Clause_1.TableSource
|
|
1052
|
+
? this.createColumnForBinding(binding, columnName)
|
|
1053
|
+
: null;
|
|
1054
|
+
}
|
|
1055
|
+
collectSourceOutputMatches(root, query, source, columnName) {
|
|
1056
|
+
var _a, _b, _c, _d;
|
|
1057
|
+
const commonTables = [
|
|
1058
|
+
...((_b = (_a = query.withClause) === null || _a === void 0 ? void 0 : _a.tables) !== null && _b !== void 0 ? _b : []),
|
|
1059
|
+
...((_d = (_c = root.withClause) === null || _c === void 0 ? void 0 : _c.tables) !== null && _d !== void 0 ? _d : [])
|
|
1060
|
+
];
|
|
1061
|
+
const collector = new SelectOutputCollector_1.SelectOutputCollector(null, commonTables.length > 0 ? commonTables : null);
|
|
1062
|
+
return collector.collect(source).filter(item => (0, PredicateExpressionUtils_1.identifiersEqual)(item.name, columnName));
|
|
1063
|
+
}
|
|
1064
|
+
createColumnForBinding(binding, columnName) {
|
|
1065
|
+
return new ValueComponent_1.ColumnReference(binding.alias ? [binding.alias] : null, columnName);
|
|
1066
|
+
}
|
|
1067
|
+
createInferredOutputColumn(name, value, outputIndex) {
|
|
1068
|
+
return {
|
|
1069
|
+
name,
|
|
1070
|
+
value,
|
|
1071
|
+
outputIndex,
|
|
1072
|
+
sourceAlias: value.getNamespace() || null,
|
|
1073
|
+
sourceName: null,
|
|
1074
|
+
sourceColumnName: name
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
926
1077
|
resolveSourceQueryForColumns(root, source) {
|
|
927
1078
|
var _a;
|
|
928
1079
|
if (source.datasource instanceof Clause_1.SubQuerySource) {
|
|
@@ -938,13 +1089,13 @@ class ParameterConditionPlacementOptimizer {
|
|
|
938
1089
|
}
|
|
939
1090
|
findCte(root, name) {
|
|
940
1091
|
var _a, _b;
|
|
941
|
-
const normalized = normalizeIdentifier(name);
|
|
1092
|
+
const normalized = (0, PredicateExpressionUtils_1.normalizeIdentifier)(name);
|
|
942
1093
|
const matches = ((_b = (_a = root.withClause) === null || _a === void 0 ? void 0 : _a.tables) !== null && _b !== void 0 ? _b : [])
|
|
943
|
-
.filter(table => normalizeIdentifier(table.getSourceAliasName()) === normalized);
|
|
1094
|
+
.filter(table => (0, PredicateExpressionUtils_1.normalizeIdentifier)(table.getSourceAliasName()) === normalized);
|
|
944
1095
|
return matches.length === 1 ? matches[0] : null;
|
|
945
1096
|
}
|
|
946
1097
|
countTableSourceReferences(query, tableName) {
|
|
947
|
-
const normalized = normalizeIdentifier(tableName);
|
|
1098
|
+
const normalized = (0, PredicateExpressionUtils_1.normalizeIdentifier)(tableName);
|
|
948
1099
|
let count = 0;
|
|
949
1100
|
const visitSelect = (select) => {
|
|
950
1101
|
var _a, _b;
|
|
@@ -959,7 +1110,7 @@ class ParameterConditionPlacementOptimizer {
|
|
|
959
1110
|
if (select.fromClause) {
|
|
960
1111
|
for (const binding of this.getSourceBindings(select.fromClause)) {
|
|
961
1112
|
const source = binding.source.datasource;
|
|
962
|
-
if (source instanceof Clause_1.TableSource && normalizeIdentifier(source.table.name) === normalized) {
|
|
1113
|
+
if (source instanceof Clause_1.TableSource && (0, PredicateExpressionUtils_1.normalizeIdentifier)(source.table.name) === normalized) {
|
|
963
1114
|
count += 1;
|
|
964
1115
|
}
|
|
965
1116
|
if (source instanceof Clause_1.SubQuerySource) {
|
|
@@ -985,7 +1136,7 @@ class ParameterConditionPlacementOptimizer {
|
|
|
985
1136
|
if (found) {
|
|
986
1137
|
return;
|
|
987
1138
|
}
|
|
988
|
-
const candidate = unwrapParens(value);
|
|
1139
|
+
const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(value);
|
|
989
1140
|
if (candidate instanceof ValueComponent_1.FunctionCall) {
|
|
990
1141
|
if (candidate.over) {
|
|
991
1142
|
found = true;
|
|
@@ -1035,9 +1186,9 @@ class ParameterConditionPlacementOptimizer {
|
|
|
1035
1186
|
collectColumnReferences(expression) {
|
|
1036
1187
|
const references = [];
|
|
1037
1188
|
const visit = (value) => {
|
|
1038
|
-
const candidate = unwrapParens(value);
|
|
1189
|
+
const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(value);
|
|
1039
1190
|
if (candidate instanceof ValueComponent_1.ColumnReference) {
|
|
1040
|
-
if (!references.some(existing => sameColumnReference(existing, candidate))) {
|
|
1191
|
+
if (!references.some(existing => (0, PredicateExpressionUtils_1.sameColumnReference)(existing, candidate))) {
|
|
1041
1192
|
references.push(candidate);
|
|
1042
1193
|
}
|
|
1043
1194
|
return;
|
|
@@ -1109,9 +1260,9 @@ class ParameterConditionPlacementOptimizer {
|
|
|
1109
1260
|
collectParameterNames(expression) {
|
|
1110
1261
|
const names = [];
|
|
1111
1262
|
const visit = (value) => {
|
|
1112
|
-
const candidate = unwrapParens(value);
|
|
1263
|
+
const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(value);
|
|
1113
1264
|
if (candidate instanceof ValueComponent_1.ParameterExpression) {
|
|
1114
|
-
appendUnique(names, candidate.name.value);
|
|
1265
|
+
(0, PredicateExpressionUtils_1.appendUnique)(names, candidate.name.value);
|
|
1115
1266
|
return;
|
|
1116
1267
|
}
|
|
1117
1268
|
if (candidate instanceof ValueComponent_1.BinaryExpression) {
|