rawsql-ts 0.29.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/ParameterConditionPlacementOptimizer.js +3 -71
- 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 +2 -0
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.js +156 -162
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.js.map +1 -1
- 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/PredicateExpressionUtils.d.ts +16 -0
- package/dist/src/transformers/PredicateReachabilityAnalyzer.d.ts +88 -0
- package/dist/src/transformers/StaticPredicatePlacementOptimizer.d.ts +2 -0
- package/dist/transformers/ParameterConditionPlacementOptimizer.js +38 -106
- 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 +190 -196
- package/dist/transformers/StaticPredicatePlacementOptimizer.js.map +1 -1
- package/dist/tsconfig.browser.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -1,80 +1,12 @@
|
|
|
1
|
-
import { DistinctOn, JoinOnClause, SubQuerySource, TableSource
|
|
1
|
+
import { DistinctOn, JoinOnClause, SubQuerySource, TableSource } from "../models/Clause";
|
|
2
2
|
import { BinarySelectQuery, SimpleSelectQuery } from "../models/SelectQuery";
|
|
3
|
-
import { ArrayExpression, ArrayQueryExpression, BetweenExpression, BinaryExpression, CaseExpression, CastExpression, ColumnReference, FunctionCall, InlineQuery, JsonPredicateExpression, ParameterExpression,
|
|
3
|
+
import { ArrayExpression, ArrayQueryExpression, BetweenExpression, BinaryExpression, CaseExpression, CastExpression, ColumnReference, FunctionCall, InlineQuery, JsonPredicateExpression, ParameterExpression, TupleExpression, TypeValue, UnaryExpression, ValueList } from "../models/ValueComponent";
|
|
4
4
|
import { SelectQueryParser } from "../parsers/SelectQueryParser";
|
|
5
|
-
import { ValueParser } from "../parsers/ValueParser";
|
|
6
5
|
import { formatSqlComponent, hasSqlComponentFormatOverride } from "./SqlComponentFormatter";
|
|
7
6
|
import { SelectOutputCollector } from "./SelectOutputCollector";
|
|
8
7
|
import { dedupeTopLevelAndConditions, dedupeWhereTopLevelAndConditions } from "./TopLevelAndConditionDeduper";
|
|
8
|
+
import { appendUnique, cloneColumnReference, cloneValueComponent, collectTopLevelAndTerms, columnReferenceText, identifiersEqual, normalizeIdentifier, rebuildWhereWithoutTerms, sameColumnReference, unwrapParens } from "./PredicateExpressionUtils";
|
|
9
9
|
const VOLATILE_OR_UNSUPPORTED_FUNCTION_REASON = "Predicate contains a function call; volatile and expression predicates are not moved in the safe-only implementation.";
|
|
10
|
-
const normalizeIdentifier = (value) => value.trim().toLowerCase();
|
|
11
|
-
const isCaseSensitiveIdentifier = (value) => /[A-Z]/.test(value.trim());
|
|
12
|
-
const identifiersEqual = (left, right) => {
|
|
13
|
-
const trimmedLeft = left.trim();
|
|
14
|
-
const trimmedRight = right.trim();
|
|
15
|
-
return isCaseSensitiveIdentifier(trimmedLeft) || isCaseSensitiveIdentifier(trimmedRight)
|
|
16
|
-
? trimmedLeft === trimmedRight
|
|
17
|
-
: trimmedLeft.toLowerCase() === trimmedRight.toLowerCase();
|
|
18
|
-
};
|
|
19
|
-
const unwrapParens = (expression) => {
|
|
20
|
-
let candidate = expression;
|
|
21
|
-
while (candidate instanceof ParenExpression) {
|
|
22
|
-
candidate = candidate.expression;
|
|
23
|
-
}
|
|
24
|
-
return candidate;
|
|
25
|
-
};
|
|
26
|
-
const isBinaryOperator = (expression, operator) => {
|
|
27
|
-
const candidate = unwrapParens(expression);
|
|
28
|
-
return candidate instanceof BinaryExpression
|
|
29
|
-
&& candidate.operator.value.trim().toLowerCase() === operator;
|
|
30
|
-
};
|
|
31
|
-
const collectTopLevelAndTerms = (expression) => {
|
|
32
|
-
const candidate = unwrapParens(expression);
|
|
33
|
-
if (!isBinaryOperator(candidate, "and")) {
|
|
34
|
-
return [expression];
|
|
35
|
-
}
|
|
36
|
-
return [
|
|
37
|
-
...collectTopLevelAndTerms(candidate.left),
|
|
38
|
-
...collectTopLevelAndTerms(candidate.right)
|
|
39
|
-
];
|
|
40
|
-
};
|
|
41
|
-
const rebuildWhereWithoutTerms = (query, termsToRemove) => {
|
|
42
|
-
if (!query.whereClause || termsToRemove.size === 0) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
const remaining = collectTopLevelAndTerms(query.whereClause.condition)
|
|
46
|
-
.filter(term => !termsToRemove.has(term));
|
|
47
|
-
if (remaining.length === 0) {
|
|
48
|
-
query.whereClause = null;
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
let rebuilt = remaining[0];
|
|
52
|
-
for (let index = 1; index < remaining.length; index += 1) {
|
|
53
|
-
rebuilt = new BinaryExpression(rebuilt, "and", remaining[index]);
|
|
54
|
-
}
|
|
55
|
-
query.whereClause = new WhereClause(rebuilt);
|
|
56
|
-
};
|
|
57
|
-
const cloneValueComponent = (expression, options) => {
|
|
58
|
-
return ValueParser.parse(formatSqlComponent(expression, options));
|
|
59
|
-
};
|
|
60
|
-
const cloneColumnReference = (reference) => {
|
|
61
|
-
var _a, _b;
|
|
62
|
-
const namespaces = (_b = (_a = reference.namespaces) === null || _a === void 0 ? void 0 : _a.map(namespace => namespace.name)) !== null && _b !== void 0 ? _b : null;
|
|
63
|
-
return new ColumnReference(namespaces, reference.column.name);
|
|
64
|
-
};
|
|
65
|
-
const columnReferenceText = (reference) => {
|
|
66
|
-
const namespace = reference.getNamespace();
|
|
67
|
-
return namespace ? `${namespace}.${reference.column.name}` : reference.column.name;
|
|
68
|
-
};
|
|
69
|
-
const sameColumnReference = (left, right) => {
|
|
70
|
-
return identifiersEqual(left.column.name, right.column.name)
|
|
71
|
-
&& identifiersEqual(left.getNamespace(), right.getNamespace());
|
|
72
|
-
};
|
|
73
|
-
const appendUnique = (items, value) => {
|
|
74
|
-
if (!items.includes(value)) {
|
|
75
|
-
items.push(value);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
10
|
export class StaticPredicatePlacementOptimizer {
|
|
79
11
|
plan(input, options = {}) {
|
|
80
12
|
var _a, _b, _c;
|
|
@@ -112,77 +44,8 @@ export class StaticPredicatePlacementOptimizer {
|
|
|
112
44
|
const query = parsed.query;
|
|
113
45
|
const applied = [];
|
|
114
46
|
const skipped = [];
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const candidate = this.analyzeCandidate(query, term, options);
|
|
118
|
-
if (!candidate) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
if ("code" in candidate) {
|
|
122
|
-
skipped.push(this.makeSkipped(term, candidate, options));
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
const target = this.resolveTarget(query, candidate);
|
|
126
|
-
if ("code" in target) {
|
|
127
|
-
skipped.push(this.makeSkipped(term, target, options));
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
let appliedReason = "";
|
|
131
|
-
if (target.kind === "join_on") {
|
|
132
|
-
this.appendJoinOnPredicate(target.join, candidate.expression, options);
|
|
133
|
-
appliedReason = target.reason;
|
|
134
|
-
}
|
|
135
|
-
else if (target.kind === "simple") {
|
|
136
|
-
const targetColumns = this.resolveTargetColumns(query, target.query, candidate.references);
|
|
137
|
-
if ("code" in targetColumns) {
|
|
138
|
-
skipped.push(this.makeSkipped(term, targetColumns, options));
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
const placement = this.resolveTargetPlacement(target.query, targetColumns);
|
|
142
|
-
if ("code" in placement) {
|
|
143
|
-
skipped.push(this.makeSkipped(term, placement, options));
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
const movedPredicate = this.rebasePredicate(candidate.expression, targetColumns, options);
|
|
147
|
-
target.query.appendWhere(movedPredicate);
|
|
148
|
-
dedupeWhereTopLevelAndConditions(target.query, options);
|
|
149
|
-
appliedReason = placement.reason;
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
const placements = [];
|
|
153
|
-
let skip = null;
|
|
154
|
-
for (const branch of target.branches) {
|
|
155
|
-
const placement = this.resolveTargetPlacement(branch.query, branch.targetColumns);
|
|
156
|
-
if ("code" in placement) {
|
|
157
|
-
skip = placement;
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
placements.push(placement);
|
|
161
|
-
}
|
|
162
|
-
if (skip) {
|
|
163
|
-
skipped.push(this.makeSkipped(term, skip, options));
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
for (const branch of target.branches) {
|
|
167
|
-
const movedPredicate = this.rebasePredicate(candidate.expression, branch.targetColumns, options);
|
|
168
|
-
branch.query.appendWhere(movedPredicate);
|
|
169
|
-
dedupeWhereTopLevelAndConditions(branch.query, options);
|
|
170
|
-
}
|
|
171
|
-
appliedReason = placements.some(item => /group by/i.test(item.reason))
|
|
172
|
-
? "Predicate is distributed to every UNION branch by output column position; grouped branches only receive GROUP BY-key predicates."
|
|
173
|
-
: "Predicate is distributed to every UNION branch by output column position before unsafe query boundaries.";
|
|
174
|
-
}
|
|
175
|
-
movedTerms.push(term);
|
|
176
|
-
applied.push({
|
|
177
|
-
kind: "move_static_predicate",
|
|
178
|
-
predicateSql: candidate.predicateSql,
|
|
179
|
-
fromScopeId: "scope:root",
|
|
180
|
-
toScopeId: target.scopeId,
|
|
181
|
-
reason: appliedReason,
|
|
182
|
-
columnReferences: candidate.references.map(columnReferenceText)
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
rebuildWhereWithoutTerms(query, new Set(movedTerms));
|
|
47
|
+
this.placePredicatesInScopes(query, options, applied, skipped);
|
|
48
|
+
// API output shape review: keep result.sql/result.query compatibility while expanding scope-aware placement.
|
|
186
49
|
const sql = applied.length > 0 || hasSqlComponentFormatOverride(options)
|
|
187
50
|
? formatSqlComponent(query, options)
|
|
188
51
|
: parsed.sql;
|
|
@@ -197,6 +60,121 @@ export class StaticPredicatePlacementOptimizer {
|
|
|
197
60
|
formatterGeneratedSource: parsed.formatterGeneratedSource
|
|
198
61
|
});
|
|
199
62
|
}
|
|
63
|
+
placePredicatesInScopes(contextRoot, options, applied, skipped) {
|
|
64
|
+
var _a;
|
|
65
|
+
const queue = [{ query: contextRoot, scopeId: "scope:root" }];
|
|
66
|
+
let processed = 0;
|
|
67
|
+
while (queue.length > 0) {
|
|
68
|
+
processed += 1;
|
|
69
|
+
if (processed > 1000) {
|
|
70
|
+
skipped.push({
|
|
71
|
+
predicateSql: "",
|
|
72
|
+
scopeId: "scope:root",
|
|
73
|
+
code: "RECURSIVE_PLACEMENT_LIMIT",
|
|
74
|
+
reason: "Static predicate recursive placement stopped after reaching the safety iteration limit."
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const work = queue.shift();
|
|
79
|
+
const movedTerms = [];
|
|
80
|
+
const reportSkipped = work.pendingTerms === undefined;
|
|
81
|
+
const terms = (_a = work.pendingTerms) !== null && _a !== void 0 ? _a : (work.query.whereClause
|
|
82
|
+
? collectTopLevelAndTerms(work.query.whereClause.condition)
|
|
83
|
+
: []);
|
|
84
|
+
for (const term of terms) {
|
|
85
|
+
const candidate = this.analyzeCandidate(work.query, term, options);
|
|
86
|
+
if (!candidate) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if ("code" in candidate) {
|
|
90
|
+
if (reportSkipped) {
|
|
91
|
+
skipped.push(this.makeSkipped(term, work.scopeId, candidate, options));
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const target = this.resolveTarget(contextRoot, work.query, candidate);
|
|
96
|
+
if ("code" in target) {
|
|
97
|
+
if (reportSkipped) {
|
|
98
|
+
skipped.push(this.makeSkipped(term, work.scopeId, target, options));
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
let appliedReason = "";
|
|
103
|
+
if (target.kind === "join_on") {
|
|
104
|
+
this.appendJoinOnPredicate(target.join, candidate.expression, options);
|
|
105
|
+
appliedReason = target.reason;
|
|
106
|
+
}
|
|
107
|
+
else if (target.kind === "simple") {
|
|
108
|
+
const targetColumns = this.resolveTargetColumns(contextRoot, target.query, candidate.references);
|
|
109
|
+
if ("code" in targetColumns) {
|
|
110
|
+
if (reportSkipped) {
|
|
111
|
+
skipped.push(this.makeSkipped(term, work.scopeId, targetColumns, options));
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const placement = this.resolveTargetPlacement(contextRoot, target.query, targetColumns);
|
|
116
|
+
if ("code" in placement) {
|
|
117
|
+
if (reportSkipped) {
|
|
118
|
+
skipped.push(this.makeSkipped(term, work.scopeId, placement, options));
|
|
119
|
+
}
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const movedPredicate = this.rebasePredicate(candidate.expression, targetColumns, options);
|
|
123
|
+
target.query.appendWhere(movedPredicate);
|
|
124
|
+
dedupeWhereTopLevelAndConditions(target.query, options);
|
|
125
|
+
appliedReason = work.scopeId === "scope:root"
|
|
126
|
+
? placement.reason
|
|
127
|
+
: `${placement.reason} Predicate was safely rebased from a previously moved predicate.`;
|
|
128
|
+
const retainedTerms = target.query.whereClause
|
|
129
|
+
? collectTopLevelAndTerms(target.query.whereClause.condition)
|
|
130
|
+
: [];
|
|
131
|
+
if (retainedTerms.includes(movedPredicate)) {
|
|
132
|
+
queue.push({
|
|
133
|
+
query: target.query,
|
|
134
|
+
scopeId: target.scopeId,
|
|
135
|
+
pendingTerms: [movedPredicate]
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const placements = [];
|
|
141
|
+
let skip = null;
|
|
142
|
+
for (const branch of target.branches) {
|
|
143
|
+
const placement = this.resolveTargetPlacement(contextRoot, branch.query, branch.targetColumns);
|
|
144
|
+
if ("code" in placement) {
|
|
145
|
+
skip = placement;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
placements.push(placement);
|
|
149
|
+
}
|
|
150
|
+
if (skip) {
|
|
151
|
+
if (reportSkipped) {
|
|
152
|
+
skipped.push(this.makeSkipped(term, work.scopeId, skip, options));
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
for (const branch of target.branches) {
|
|
157
|
+
const movedPredicate = this.rebasePredicate(candidate.expression, branch.targetColumns, options);
|
|
158
|
+
branch.query.appendWhere(movedPredicate);
|
|
159
|
+
dedupeWhereTopLevelAndConditions(branch.query, options);
|
|
160
|
+
}
|
|
161
|
+
appliedReason = placements.some(item => /group by/i.test(item.reason))
|
|
162
|
+
? "Predicate is distributed to every UNION branch by output column position; grouped branches only receive GROUP BY-key predicates."
|
|
163
|
+
: "Predicate is distributed to every UNION branch by output column position before unsafe query boundaries.";
|
|
164
|
+
}
|
|
165
|
+
movedTerms.push(term);
|
|
166
|
+
applied.push({
|
|
167
|
+
kind: "move_static_predicate",
|
|
168
|
+
predicateSql: candidate.predicateSql,
|
|
169
|
+
fromScopeId: work.scopeId,
|
|
170
|
+
toScopeId: target.scopeId,
|
|
171
|
+
reason: appliedReason,
|
|
172
|
+
columnReferences: candidate.references.map(columnReferenceText)
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
rebuildWhereWithoutTerms(work.query, new Set(movedTerms));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
200
178
|
optimize(input, options = {}) {
|
|
201
179
|
var _a;
|
|
202
180
|
return this.plan(input, Object.assign(Object.assign({}, options), { dryRun: (_a = options.dryRun) !== null && _a !== void 0 ? _a : false }));
|
|
@@ -392,13 +370,13 @@ export class StaticPredicatePlacementOptimizer {
|
|
|
392
370
|
visit(expression);
|
|
393
371
|
return found;
|
|
394
372
|
}
|
|
395
|
-
resolveTarget(
|
|
373
|
+
resolveTarget(contextRoot, currentQuery, candidate) {
|
|
396
374
|
var _a;
|
|
397
|
-
const boundary = this.findRootQueryBoundary(
|
|
375
|
+
const boundary = this.findRootQueryBoundary(currentQuery);
|
|
398
376
|
if (boundary) {
|
|
399
377
|
return boundary;
|
|
400
378
|
}
|
|
401
|
-
if (!
|
|
379
|
+
if (!currentQuery.fromClause) {
|
|
402
380
|
return {
|
|
403
381
|
code: "NO_FROM_CLAUSE",
|
|
404
382
|
reason: "Predicate has no FROM source that can receive it safely."
|
|
@@ -406,7 +384,7 @@ export class StaticPredicatePlacementOptimizer {
|
|
|
406
384
|
}
|
|
407
385
|
const bindings = [];
|
|
408
386
|
for (const reference of candidate.references) {
|
|
409
|
-
const binding = this.resolveSourceBinding(
|
|
387
|
+
const binding = this.resolveSourceBinding(contextRoot, currentQuery, reference);
|
|
410
388
|
if ("code" in binding) {
|
|
411
389
|
return binding;
|
|
412
390
|
}
|
|
@@ -427,14 +405,14 @@ export class StaticPredicatePlacementOptimizer {
|
|
|
427
405
|
reason: "Predicate crosses a LATERAL JOIN boundary; moving it may change semantics."
|
|
428
406
|
};
|
|
429
407
|
}
|
|
430
|
-
const nullableSide = this.findNullableSideBoundary(
|
|
408
|
+
const nullableSide = this.findNullableSideBoundary(currentQuery.fromClause, binding);
|
|
431
409
|
if (nullableSide) {
|
|
432
410
|
return nullableSide;
|
|
433
411
|
}
|
|
434
|
-
const upstream = this.resolveUpstreamQuery(
|
|
412
|
+
const upstream = this.resolveUpstreamQuery(contextRoot, binding, candidate.references);
|
|
435
413
|
if ("code" in upstream) {
|
|
436
414
|
if (upstream.code === "NO_SAFE_UPSTREAM_QUERY") {
|
|
437
|
-
const joinOnTarget = this.resolveBaseTableJoinOnTarget(
|
|
415
|
+
const joinOnTarget = this.resolveBaseTableJoinOnTarget(contextRoot, currentQuery, binding);
|
|
438
416
|
if (!("code" in joinOnTarget)) {
|
|
439
417
|
return joinOnTarget;
|
|
440
418
|
}
|
|
@@ -459,7 +437,7 @@ export class StaticPredicatePlacementOptimizer {
|
|
|
459
437
|
}
|
|
460
438
|
return null;
|
|
461
439
|
}
|
|
462
|
-
resolveTargetPlacement(query, targetColumns) {
|
|
440
|
+
resolveTargetPlacement(contextRoot, query, targetColumns) {
|
|
463
441
|
const hasOrdinaryDistinct = this.hasOrdinaryDistinct(query);
|
|
464
442
|
if (this.hasDistinctOnBoundary(query)) {
|
|
465
443
|
return {
|
|
@@ -479,11 +457,11 @@ export class StaticPredicatePlacementOptimizer {
|
|
|
479
457
|
reason: "Predicate crosses LIMIT/OFFSET/FETCH boundary; moving it may change row selection semantics."
|
|
480
458
|
};
|
|
481
459
|
}
|
|
482
|
-
if (query.fromClause
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
460
|
+
if (query.fromClause) {
|
|
461
|
+
const nullableSide = this.findNullableSideBoundaryForTargetColumns(contextRoot, query, targetColumns);
|
|
462
|
+
if (nullableSide) {
|
|
463
|
+
return nullableSide;
|
|
464
|
+
}
|
|
487
465
|
}
|
|
488
466
|
if (query.groupByClause) {
|
|
489
467
|
const allReferencesAreGroupKeys = targetColumns.every(item => this.isGroupKeyColumn(query, item.targetColumn));
|
|
@@ -512,6 +490,22 @@ export class StaticPredicatePlacementOptimizer {
|
|
|
512
490
|
reason: "All outer references resolve to direct upstream outputs before unsafe query boundaries."
|
|
513
491
|
};
|
|
514
492
|
}
|
|
493
|
+
findNullableSideBoundaryForTargetColumns(contextRoot, query, targetColumns) {
|
|
494
|
+
if (!query.fromClause) {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
for (const item of targetColumns) {
|
|
498
|
+
const binding = this.resolveSourceBinding(contextRoot, query, item.targetColumn);
|
|
499
|
+
if ("code" in binding) {
|
|
500
|
+
return binding;
|
|
501
|
+
}
|
|
502
|
+
const nullableSide = this.findNullableSideBoundary(query.fromClause, binding);
|
|
503
|
+
if (nullableSide) {
|
|
504
|
+
return nullableSide;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
515
509
|
resolveSourceBinding(contextRoot, query, column) {
|
|
516
510
|
const fromClause = query.fromClause;
|
|
517
511
|
if (!fromClause) {
|
|
@@ -690,22 +684,22 @@ export class StaticPredicatePlacementOptimizer {
|
|
|
690
684
|
}
|
|
691
685
|
return resolved;
|
|
692
686
|
}
|
|
693
|
-
resolveBaseTableJoinOnTarget(
|
|
687
|
+
resolveBaseTableJoinOnTarget(contextRoot, currentQuery, binding) {
|
|
694
688
|
var _a, _b, _c;
|
|
695
|
-
if (!
|
|
689
|
+
if (!currentQuery.fromClause || !this.isBaseTableBinding(contextRoot, binding)) {
|
|
696
690
|
return {
|
|
697
691
|
code: "NO_SAFE_UPSTREAM_QUERY",
|
|
698
692
|
reason: "The referenced source is a base table, so there is no upstream query block to move into."
|
|
699
693
|
};
|
|
700
694
|
}
|
|
701
|
-
if (this.hasOuterJoin(
|
|
695
|
+
if (this.hasOuterJoin(currentQuery.fromClause)) {
|
|
702
696
|
return {
|
|
703
697
|
code: "OUTER_JOIN_BOUNDARY",
|
|
704
698
|
reason: "Predicate crosses an OUTER JOIN boundary; moving it into JOIN ON may change semantics."
|
|
705
699
|
};
|
|
706
700
|
}
|
|
707
701
|
const join = binding.isPrimary
|
|
708
|
-
? (_b = (_a =
|
|
702
|
+
? (_b = (_a = currentQuery.fromClause.joins) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : null
|
|
709
703
|
: binding.join;
|
|
710
704
|
if (join === null || join === void 0 ? void 0 : join.lateral) {
|
|
711
705
|
return {
|
|
@@ -778,7 +772,7 @@ export class StaticPredicatePlacementOptimizer {
|
|
|
778
772
|
if ("code" in upstreamColumns) {
|
|
779
773
|
return { query, targetColumns: [...targetColumns] };
|
|
780
774
|
}
|
|
781
|
-
const placement = this.resolveTargetPlacement(upstream.query, upstreamColumns);
|
|
775
|
+
const placement = this.resolveTargetPlacement(contextRoot, upstream.query, upstreamColumns);
|
|
782
776
|
if ("code" in placement) {
|
|
783
777
|
return { query, targetColumns: [...targetColumns] };
|
|
784
778
|
}
|
|
@@ -1597,8 +1591,8 @@ export class StaticPredicatePlacementOptimizer {
|
|
|
1597
1591
|
visit(expression);
|
|
1598
1592
|
return names;
|
|
1599
1593
|
}
|
|
1600
|
-
makeSkipped(expression, draft, options) {
|
|
1601
|
-
return Object.assign({ predicateSql: formatSqlComponent(expression, options), scopeId
|
|
1594
|
+
makeSkipped(expression, scopeId, draft, options) {
|
|
1595
|
+
return Object.assign({ predicateSql: formatSqlComponent(expression, options), scopeId }, draft);
|
|
1602
1596
|
}
|
|
1603
1597
|
buildResult(params) {
|
|
1604
1598
|
return {
|