rawsql-ts 0.26.1 → 0.27.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.map +1 -1
- package/dist/esm/index.min.js +1 -1
- package/dist/esm/index.min.js.map +3 -3
- package/dist/esm/transformers/ConditionOptimization.d.ts +1 -0
- package/dist/esm/transformers/ConditionOptimization.js +97 -57
- package/dist/esm/transformers/ConditionOptimization.js.map +1 -1
- package/dist/esm/transformers/ParameterConditionPlacementOptimizer.d.ts +26 -7
- package/dist/esm/transformers/ParameterConditionPlacementOptimizer.js +408 -138
- package/dist/esm/transformers/ParameterConditionPlacementOptimizer.js.map +1 -1
- package/dist/esm/transformers/SqlComponentFormatter.d.ts +9 -1
- package/dist/esm/transformers/SqlComponentFormatter.js +10 -1
- package/dist/esm/transformers/SqlComponentFormatter.js.map +1 -1
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.d.ts +22 -4
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.js +332 -95
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +3 -3
- package/dist/src/index.d.ts +1 -0
- package/dist/src/transformers/ConditionOptimization.d.ts +1 -0
- package/dist/src/transformers/ParameterConditionPlacementOptimizer.d.ts +26 -7
- package/dist/src/transformers/SqlComponentFormatter.d.ts +9 -1
- package/dist/src/transformers/StaticPredicatePlacementOptimizer.d.ts +22 -4
- package/dist/transformers/ConditionOptimization.js +107 -54
- package/dist/transformers/ConditionOptimization.js.map +1 -1
- package/dist/transformers/ParameterConditionPlacementOptimizer.js +406 -136
- package/dist/transformers/ParameterConditionPlacementOptimizer.js.map +1 -1
- package/dist/transformers/SqlComponentFormatter.js +12 -2
- package/dist/transformers/SqlComponentFormatter.js.map +1 -1
- package/dist/transformers/StaticPredicatePlacementOptimizer.js +330 -93
- package/dist/transformers/StaticPredicatePlacementOptimizer.js.map +1 -1
- package/dist/tsconfig.browser.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ const ValueComponent_1 = require("../models/ValueComponent");
|
|
|
7
7
|
const SelectQueryParser_1 = require("../parsers/SelectQueryParser");
|
|
8
8
|
const ValueParser_1 = require("../parsers/ValueParser");
|
|
9
9
|
const SqlComponentFormatter_1 = require("./SqlComponentFormatter");
|
|
10
|
+
const SelectOutputCollector_1 = require("./SelectOutputCollector");
|
|
10
11
|
const VOLATILE_OR_UNSUPPORTED_FUNCTION_REASON = "Predicate contains a function call; volatile and expression predicates are not moved in the safe-only implementation.";
|
|
11
12
|
const normalizeIdentifier = (value) => value.trim().toLowerCase();
|
|
12
13
|
const unwrapParens = (expression) => {
|
|
@@ -47,8 +48,8 @@ const rebuildWhereWithoutTerms = (query, termsToRemove) => {
|
|
|
47
48
|
}
|
|
48
49
|
query.whereClause = new Clause_1.WhereClause(rebuilt);
|
|
49
50
|
};
|
|
50
|
-
const cloneValueComponent = (expression) => {
|
|
51
|
-
return ValueParser_1.ValueParser.parse((0, SqlComponentFormatter_1.formatSqlComponent)(expression));
|
|
51
|
+
const cloneValueComponent = (expression, options) => {
|
|
52
|
+
return ValueParser_1.ValueParser.parse((0, SqlComponentFormatter_1.formatSqlComponent)(expression, options));
|
|
52
53
|
};
|
|
53
54
|
const cloneColumnReference = (reference) => {
|
|
54
55
|
var _a, _b;
|
|
@@ -71,11 +72,12 @@ const appendUnique = (items, value) => {
|
|
|
71
72
|
class StaticPredicatePlacementOptimizer {
|
|
72
73
|
plan(input, options = {}) {
|
|
73
74
|
var _a, _b, _c;
|
|
74
|
-
const parsed = this.parseInput(input);
|
|
75
|
+
const parsed = this.parseInput(input, options);
|
|
75
76
|
const warnings = [...parsed.warnings];
|
|
76
77
|
const errors = [...parsed.errors];
|
|
77
78
|
if (!parsed.query) {
|
|
78
79
|
return this.buildResult({
|
|
80
|
+
query: null,
|
|
79
81
|
sql: parsed.sql,
|
|
80
82
|
applied: [],
|
|
81
83
|
skipped: [],
|
|
@@ -91,6 +93,7 @@ class StaticPredicatePlacementOptimizer {
|
|
|
91
93
|
message: "Static predicate placement currently supports only SimpleSelectQuery roots."
|
|
92
94
|
});
|
|
93
95
|
return this.buildResult({
|
|
96
|
+
query: parsed.query,
|
|
94
97
|
sql: parsed.sql,
|
|
95
98
|
applied: [],
|
|
96
99
|
skipped: [],
|
|
@@ -105,39 +108,74 @@ class StaticPredicatePlacementOptimizer {
|
|
|
105
108
|
const skipped = [];
|
|
106
109
|
const movedTerms = [];
|
|
107
110
|
for (const term of query.whereClause ? collectTopLevelAndTerms(query.whereClause.condition) : []) {
|
|
108
|
-
const candidate = this.analyzeCandidate(query, term);
|
|
111
|
+
const candidate = this.analyzeCandidate(query, term, options);
|
|
109
112
|
if (!candidate) {
|
|
110
113
|
continue;
|
|
111
114
|
}
|
|
112
115
|
if ("code" in candidate) {
|
|
113
|
-
skipped.push(this.makeSkipped(term, candidate));
|
|
116
|
+
skipped.push(this.makeSkipped(term, candidate, options));
|
|
114
117
|
continue;
|
|
115
118
|
}
|
|
116
119
|
const target = this.resolveTarget(query, candidate);
|
|
117
120
|
if ("code" in target) {
|
|
118
|
-
skipped.push(this.makeSkipped(term, target));
|
|
121
|
+
skipped.push(this.makeSkipped(term, target, options));
|
|
119
122
|
continue;
|
|
120
123
|
}
|
|
121
|
-
|
|
122
|
-
if ("
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
let appliedReason = "";
|
|
125
|
+
if (target.kind === "simple") {
|
|
126
|
+
const targetColumns = this.resolveTargetColumns(query, target.query, candidate.references);
|
|
127
|
+
if ("code" in targetColumns) {
|
|
128
|
+
skipped.push(this.makeSkipped(term, targetColumns, options));
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const placement = this.resolveTargetPlacement(target.query, targetColumns);
|
|
132
|
+
if ("code" in placement) {
|
|
133
|
+
skipped.push(this.makeSkipped(term, placement, options));
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const movedPredicate = this.rebasePredicate(candidate.expression, targetColumns, options);
|
|
137
|
+
target.query.appendWhere(movedPredicate);
|
|
138
|
+
appliedReason = placement.reason;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
const placements = [];
|
|
142
|
+
let skip = null;
|
|
143
|
+
for (const branch of target.branches) {
|
|
144
|
+
const placement = this.resolveTargetPlacement(branch.query, branch.targetColumns);
|
|
145
|
+
if ("code" in placement) {
|
|
146
|
+
skip = placement;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
placements.push(placement);
|
|
150
|
+
}
|
|
151
|
+
if (skip) {
|
|
152
|
+
skipped.push(this.makeSkipped(term, skip, options));
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
for (const branch of target.branches) {
|
|
156
|
+
const movedPredicate = this.rebasePredicate(candidate.expression, branch.targetColumns, options);
|
|
157
|
+
branch.query.appendWhere(movedPredicate);
|
|
158
|
+
}
|
|
159
|
+
appliedReason = placements.some(item => /group by/i.test(item.reason))
|
|
160
|
+
? "Predicate is distributed to every UNION branch by output column position; grouped branches only receive GROUP BY-key predicates."
|
|
161
|
+
: "Predicate is distributed to every UNION branch by output column position before unsafe query boundaries.";
|
|
125
162
|
}
|
|
126
|
-
const movedPredicate = this.rebasePredicate(candidate.expression, targetColumns);
|
|
127
|
-
target.query.appendWhere(movedPredicate);
|
|
128
163
|
movedTerms.push(term);
|
|
129
164
|
applied.push({
|
|
130
165
|
kind: "move_static_predicate",
|
|
131
166
|
predicateSql: candidate.predicateSql,
|
|
132
167
|
fromScopeId: "scope:root",
|
|
133
168
|
toScopeId: target.scopeId,
|
|
134
|
-
reason:
|
|
169
|
+
reason: appliedReason,
|
|
135
170
|
columnReferences: candidate.references.map(columnReferenceText)
|
|
136
171
|
});
|
|
137
172
|
}
|
|
138
173
|
rebuildWhereWithoutTerms(query, new Set(movedTerms));
|
|
139
|
-
const sql = applied.length > 0
|
|
174
|
+
const sql = applied.length > 0 || (0, SqlComponentFormatter_1.hasSqlComponentFormatOverride)(options)
|
|
175
|
+
? (0, SqlComponentFormatter_1.formatSqlComponent)(query, options)
|
|
176
|
+
: parsed.sql;
|
|
140
177
|
return this.buildResult({
|
|
178
|
+
query,
|
|
141
179
|
sql,
|
|
142
180
|
applied,
|
|
143
181
|
skipped,
|
|
@@ -151,13 +189,22 @@ class StaticPredicatePlacementOptimizer {
|
|
|
151
189
|
var _a;
|
|
152
190
|
return this.plan(input, { ...options, dryRun: (_a = options.dryRun) !== null && _a !== void 0 ? _a : false });
|
|
153
191
|
}
|
|
154
|
-
parseInput(input) {
|
|
192
|
+
parseInput(input, options) {
|
|
155
193
|
const warnings = [];
|
|
156
194
|
const errors = [];
|
|
157
195
|
try {
|
|
158
196
|
const sourceSql = typeof input === "string"
|
|
159
197
|
? input
|
|
160
|
-
: (0, SqlComponentFormatter_1.formatSqlComponent)(input);
|
|
198
|
+
: (0, SqlComponentFormatter_1.formatSqlComponent)(input, options);
|
|
199
|
+
if (typeof input !== "string" && options.cloneInput === false) {
|
|
200
|
+
return {
|
|
201
|
+
query: input,
|
|
202
|
+
sql: sourceSql,
|
|
203
|
+
formatterGeneratedSource: false,
|
|
204
|
+
warnings,
|
|
205
|
+
errors
|
|
206
|
+
};
|
|
207
|
+
}
|
|
161
208
|
if (typeof input !== "string") {
|
|
162
209
|
warnings.push({
|
|
163
210
|
code: "AST_INPUT_FORMATTED",
|
|
@@ -188,7 +235,7 @@ class StaticPredicatePlacementOptimizer {
|
|
|
188
235
|
};
|
|
189
236
|
}
|
|
190
237
|
}
|
|
191
|
-
analyzeCandidate(root, expression) {
|
|
238
|
+
analyzeCandidate(root, expression, options) {
|
|
192
239
|
if (this.collectParameterNames(expression).length > 0) {
|
|
193
240
|
return null;
|
|
194
241
|
}
|
|
@@ -205,7 +252,7 @@ class StaticPredicatePlacementOptimizer {
|
|
|
205
252
|
}
|
|
206
253
|
return {
|
|
207
254
|
expression,
|
|
208
|
-
predicateSql: (0, SqlComponentFormatter_1.formatSqlComponent)(expression),
|
|
255
|
+
predicateSql: (0, SqlComponentFormatter_1.formatSqlComponent)(expression, options),
|
|
209
256
|
references
|
|
210
257
|
};
|
|
211
258
|
}
|
|
@@ -266,13 +313,6 @@ class StaticPredicatePlacementOptimizer {
|
|
|
266
313
|
}
|
|
267
314
|
const candidate = unwrapParens(value);
|
|
268
315
|
if (candidate instanceof ValueComponent_1.BinaryExpression) {
|
|
269
|
-
if (candidate.operator.value.trim().toLowerCase() === "or") {
|
|
270
|
-
found = {
|
|
271
|
-
code: "OR_PREDICATE_UNSUPPORTED",
|
|
272
|
-
reason: "Predicate contains OR predicates, which are not moved in the first safe-only implementation."
|
|
273
|
-
};
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
316
|
visit(candidate.left);
|
|
277
317
|
visit(candidate.right);
|
|
278
318
|
return;
|
|
@@ -309,13 +349,6 @@ class StaticPredicatePlacementOptimizer {
|
|
|
309
349
|
};
|
|
310
350
|
return;
|
|
311
351
|
}
|
|
312
|
-
if (candidate instanceof ValueComponent_1.BetweenExpression) {
|
|
313
|
-
found = {
|
|
314
|
-
code: "BETWEEN_PREDICATE_UNSUPPORTED",
|
|
315
|
-
reason: "BETWEEN predicates are not moved in the first safe-only implementation."
|
|
316
|
-
};
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
352
|
if (candidate instanceof ValueComponent_1.UnaryExpression) {
|
|
320
353
|
visit(candidate.expression);
|
|
321
354
|
return;
|
|
@@ -348,7 +381,7 @@ class StaticPredicatePlacementOptimizer {
|
|
|
348
381
|
return found;
|
|
349
382
|
}
|
|
350
383
|
resolveTarget(root, candidate) {
|
|
351
|
-
const boundary = this.
|
|
384
|
+
const boundary = this.findRootQueryBoundary(root);
|
|
352
385
|
if (boundary) {
|
|
353
386
|
return boundary;
|
|
354
387
|
}
|
|
@@ -360,7 +393,7 @@ class StaticPredicatePlacementOptimizer {
|
|
|
360
393
|
}
|
|
361
394
|
const bindings = [];
|
|
362
395
|
for (const reference of candidate.references) {
|
|
363
|
-
const binding = this.resolveSourceBinding(root, reference);
|
|
396
|
+
const binding = this.resolveSourceBinding(root, root, reference);
|
|
364
397
|
if ("code" in binding) {
|
|
365
398
|
return binding;
|
|
366
399
|
}
|
|
@@ -379,27 +412,17 @@ class StaticPredicatePlacementOptimizer {
|
|
|
379
412
|
if (nullableSide) {
|
|
380
413
|
return nullableSide;
|
|
381
414
|
}
|
|
382
|
-
const upstream = this.resolveUpstreamQuery(root, binding);
|
|
415
|
+
const upstream = this.resolveUpstreamQuery(root, binding, candidate.references);
|
|
383
416
|
if ("code" in upstream) {
|
|
384
417
|
return upstream;
|
|
385
418
|
}
|
|
386
|
-
const targetBoundary = this.findTargetQueryBoundary(upstream.query);
|
|
387
|
-
if (targetBoundary) {
|
|
388
|
-
return targetBoundary;
|
|
389
|
-
}
|
|
390
419
|
return upstream;
|
|
391
420
|
}
|
|
392
|
-
|
|
393
|
-
if (query
|
|
421
|
+
findRootQueryBoundary(query) {
|
|
422
|
+
if (this.hasDistinctOnBoundary(query)) {
|
|
394
423
|
return {
|
|
395
424
|
code: "DISTINCT_BOUNDARY",
|
|
396
|
-
reason: "Predicate crosses DISTINCT boundary; moving it may change semantics."
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
if (query.groupByClause || query.havingClause) {
|
|
400
|
-
return {
|
|
401
|
-
code: "GROUP_BY_BOUNDARY",
|
|
402
|
-
reason: "Predicate crosses GROUP BY boundary; moving it may change semantics."
|
|
425
|
+
reason: "Predicate crosses DISTINCT ON boundary; moving it may change semantics."
|
|
403
426
|
};
|
|
404
427
|
}
|
|
405
428
|
if (this.hasWindowUsage(query)) {
|
|
@@ -410,10 +433,19 @@ class StaticPredicatePlacementOptimizer {
|
|
|
410
433
|
}
|
|
411
434
|
return null;
|
|
412
435
|
}
|
|
413
|
-
|
|
414
|
-
const
|
|
415
|
-
if (
|
|
416
|
-
return
|
|
436
|
+
resolveTargetPlacement(query, targetColumns) {
|
|
437
|
+
const hasOrdinaryDistinct = this.hasOrdinaryDistinct(query);
|
|
438
|
+
if (this.hasDistinctOnBoundary(query)) {
|
|
439
|
+
return {
|
|
440
|
+
code: "DISTINCT_BOUNDARY",
|
|
441
|
+
reason: "Predicate crosses DISTINCT ON boundary; moving it may change semantics."
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
if (this.hasWindowUsage(query)) {
|
|
445
|
+
return {
|
|
446
|
+
code: "WINDOW_BOUNDARY",
|
|
447
|
+
reason: "Predicate crosses WINDOW boundary; moving it may change semantics."
|
|
448
|
+
};
|
|
417
449
|
}
|
|
418
450
|
if (query.limitClause || query.offsetClause || query.fetchClause) {
|
|
419
451
|
return {
|
|
@@ -427,10 +459,35 @@ class StaticPredicatePlacementOptimizer {
|
|
|
427
459
|
reason: "Target query contains an OUTER JOIN boundary that is not moved across in the safe-only implementation."
|
|
428
460
|
};
|
|
429
461
|
}
|
|
430
|
-
|
|
462
|
+
if (query.groupByClause) {
|
|
463
|
+
const allReferencesAreGroupKeys = targetColumns.every(item => this.isGroupKeyColumn(query, item.targetColumn));
|
|
464
|
+
if (!allReferencesAreGroupKeys) {
|
|
465
|
+
return {
|
|
466
|
+
code: "GROUP_BY_BOUNDARY",
|
|
467
|
+
reason: "Predicate references a target column that is not proven to be a GROUP BY key."
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
reason: "Predicate references only GROUP BY keys; it is moved into pre-aggregation WHERE."
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
if (query.havingClause) {
|
|
475
|
+
return {
|
|
476
|
+
code: "GROUP_BY_BOUNDARY",
|
|
477
|
+
reason: "Predicate crosses HAVING aggregation boundary; moving it may change semantics."
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (hasOrdinaryDistinct) {
|
|
481
|
+
return {
|
|
482
|
+
reason: "Predicate references direct ordinary DISTINCT output columns; it is moved into the DISTINCT input WHERE."
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
reason: "All outer references resolve to direct upstream outputs before unsafe query boundaries."
|
|
487
|
+
};
|
|
431
488
|
}
|
|
432
|
-
resolveSourceBinding(
|
|
433
|
-
const fromClause =
|
|
489
|
+
resolveSourceBinding(contextRoot, query, column) {
|
|
490
|
+
const fromClause = query.fromClause;
|
|
434
491
|
if (!fromClause) {
|
|
435
492
|
return {
|
|
436
493
|
code: "NO_FROM_CLAUSE",
|
|
@@ -448,10 +505,7 @@ class StaticPredicatePlacementOptimizer {
|
|
|
448
505
|
reason: `Column source alias '${column.getNamespace()}' is not uniquely resolvable.`
|
|
449
506
|
};
|
|
450
507
|
}
|
|
451
|
-
|
|
452
|
-
return matches[0];
|
|
453
|
-
}
|
|
454
|
-
const matchCount = this.getOutputColumnMatchCount(root, matches[0], columnName);
|
|
508
|
+
const matchCount = this.getOutputColumnMatchCount(contextRoot, matches[0], columnName);
|
|
455
509
|
if (matchCount === 0) {
|
|
456
510
|
return {
|
|
457
511
|
code: "COLUMN_NOT_AVAILABLE_UPSTREAM",
|
|
@@ -467,13 +521,8 @@ class StaticPredicatePlacementOptimizer {
|
|
|
467
521
|
return matches[0];
|
|
468
522
|
}
|
|
469
523
|
const matches = [];
|
|
470
|
-
let unionSource = null;
|
|
471
524
|
for (const binding of bindings) {
|
|
472
|
-
|
|
473
|
-
unionSource !== null && unionSource !== void 0 ? unionSource : (unionSource = binding);
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
const matchCount = this.getOutputColumnMatchCount(root, binding, columnName);
|
|
525
|
+
const matchCount = this.getOutputColumnMatchCount(contextRoot, binding, columnName);
|
|
477
526
|
if (matchCount > 1) {
|
|
478
527
|
return {
|
|
479
528
|
code: "AMBIGUOUS_COLUMN_REFERENCE",
|
|
@@ -484,12 +533,6 @@ class StaticPredicatePlacementOptimizer {
|
|
|
484
533
|
matches.push(binding);
|
|
485
534
|
}
|
|
486
535
|
}
|
|
487
|
-
if (matches.length === 0 && unionSource) {
|
|
488
|
-
return {
|
|
489
|
-
code: "UNION_BOUNDARY",
|
|
490
|
-
reason: "Predicate would need distribution into a UNION branch, which is unsupported."
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
536
|
if (matches.length !== 1) {
|
|
494
537
|
return {
|
|
495
538
|
code: "AMBIGUOUS_COLUMN_REFERENCE",
|
|
@@ -500,16 +543,20 @@ class StaticPredicatePlacementOptimizer {
|
|
|
500
543
|
}
|
|
501
544
|
return matches[0];
|
|
502
545
|
}
|
|
503
|
-
resolveUpstreamQuery(root, binding) {
|
|
546
|
+
resolveUpstreamQuery(root, binding, references) {
|
|
504
547
|
const source = binding.source.datasource;
|
|
505
548
|
if (source instanceof Clause_1.SubQuerySource) {
|
|
506
549
|
if (source.query instanceof SelectQuery_1.SimpleSelectQuery) {
|
|
507
550
|
return {
|
|
551
|
+
kind: "simple",
|
|
508
552
|
query: source.query,
|
|
509
553
|
scopeId: `subquery:${binding.alias}`,
|
|
510
554
|
sourceBinding: binding
|
|
511
555
|
};
|
|
512
556
|
}
|
|
557
|
+
if (source.query instanceof SelectQuery_1.BinarySelectQuery) {
|
|
558
|
+
return this.resolveUnionTarget(root, source.query, `subquery:${binding.alias}`, references);
|
|
559
|
+
}
|
|
513
560
|
return {
|
|
514
561
|
code: "UNION_BOUNDARY",
|
|
515
562
|
reason: "Predicate would need distribution into a UNION or non-simple subquery, which is unsupported."
|
|
@@ -537,10 +584,7 @@ class StaticPredicatePlacementOptimizer {
|
|
|
537
584
|
};
|
|
538
585
|
}
|
|
539
586
|
if (commonTable.query instanceof SelectQuery_1.BinarySelectQuery) {
|
|
540
|
-
return {
|
|
541
|
-
code: "UNION_BOUNDARY",
|
|
542
|
-
reason: "Predicate would need distribution into a UNION branch, which is unsupported."
|
|
543
|
-
};
|
|
587
|
+
return this.resolveUnionTarget(root, commonTable.query, `cte:${commonTable.getSourceAliasName()}`, references);
|
|
544
588
|
}
|
|
545
589
|
if (!(commonTable.query instanceof SelectQuery_1.SimpleSelectQuery)) {
|
|
546
590
|
return {
|
|
@@ -549,16 +593,48 @@ class StaticPredicatePlacementOptimizer {
|
|
|
549
593
|
};
|
|
550
594
|
}
|
|
551
595
|
return {
|
|
596
|
+
kind: "simple",
|
|
552
597
|
query: commonTable.query,
|
|
553
598
|
scopeId: `cte:${commonTable.getSourceAliasName()}`,
|
|
554
599
|
sourceBinding: binding,
|
|
555
600
|
cteName: commonTable.getSourceAliasName()
|
|
556
601
|
};
|
|
557
602
|
}
|
|
558
|
-
|
|
603
|
+
resolveUnionTarget(root, query, scopeId, references) {
|
|
604
|
+
const branches = this.collectUnionBranches(query);
|
|
605
|
+
if ("code" in branches) {
|
|
606
|
+
return branches;
|
|
607
|
+
}
|
|
608
|
+
const outputIndexes = new Map();
|
|
609
|
+
for (const reference of references) {
|
|
610
|
+
const outputIndex = this.resolveUnionOutputIndex(root, branches[0], reference.column.name);
|
|
611
|
+
if ("code" in outputIndex) {
|
|
612
|
+
return outputIndex;
|
|
613
|
+
}
|
|
614
|
+
outputIndexes.set(reference, outputIndex.index);
|
|
615
|
+
}
|
|
616
|
+
const branchTargets = [];
|
|
617
|
+
for (const branch of branches) {
|
|
618
|
+
const targetColumns = [];
|
|
619
|
+
for (const [reference, outputIndex] of outputIndexes.entries()) {
|
|
620
|
+
const targetColumn = this.resolveTargetColumnByOutputIndex(root, branch, outputIndex, reference);
|
|
621
|
+
if ("code" in targetColumn) {
|
|
622
|
+
return targetColumn;
|
|
623
|
+
}
|
|
624
|
+
targetColumns.push(targetColumn);
|
|
625
|
+
}
|
|
626
|
+
branchTargets.push(this.resolveDeepestBranchTarget(root, branch, targetColumns));
|
|
627
|
+
}
|
|
628
|
+
return {
|
|
629
|
+
kind: "union",
|
|
630
|
+
scopeId,
|
|
631
|
+
branches: branchTargets
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
resolveTargetColumns(root, query, references) {
|
|
559
635
|
const resolved = [];
|
|
560
636
|
for (const reference of references) {
|
|
561
|
-
const matches = query.
|
|
637
|
+
const matches = this.collectSelectOutputs(root, query).filter(item => normalizeIdentifier(item.name) === normalizeIdentifier(reference.column.name));
|
|
562
638
|
if (matches.length !== 1) {
|
|
563
639
|
return {
|
|
564
640
|
code: "AMBIGUOUS_TARGET_COLUMN",
|
|
@@ -585,6 +661,50 @@ class StaticPredicatePlacementOptimizer {
|
|
|
585
661
|
}
|
|
586
662
|
return resolved;
|
|
587
663
|
}
|
|
664
|
+
resolveDeepestBranchTarget(contextRoot, query, targetColumns, visited = new Set()) {
|
|
665
|
+
if (visited.has(query) || targetColumns.length === 0) {
|
|
666
|
+
return { query, targetColumns: [...targetColumns] };
|
|
667
|
+
}
|
|
668
|
+
const nextVisited = new Set(visited);
|
|
669
|
+
nextVisited.add(query);
|
|
670
|
+
const bindings = [];
|
|
671
|
+
for (const item of targetColumns) {
|
|
672
|
+
const binding = this.resolveSourceBinding(contextRoot, query, item.targetColumn);
|
|
673
|
+
if ("code" in binding) {
|
|
674
|
+
return { query, targetColumns: [...targetColumns] };
|
|
675
|
+
}
|
|
676
|
+
if (!bindings.some(existing => existing.source === binding.source)) {
|
|
677
|
+
bindings.push(binding);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (bindings.length !== 1) {
|
|
681
|
+
return { query, targetColumns: [...targetColumns] };
|
|
682
|
+
}
|
|
683
|
+
const binding = bindings[0];
|
|
684
|
+
const nullableSide = query.fromClause
|
|
685
|
+
? this.findNullableSideBoundary(query.fromClause, binding)
|
|
686
|
+
: null;
|
|
687
|
+
if (nullableSide) {
|
|
688
|
+
return { query, targetColumns: [...targetColumns] };
|
|
689
|
+
}
|
|
690
|
+
const upstream = this.resolveUpstreamQuery(contextRoot, binding, targetColumns.map(item => item.targetColumn));
|
|
691
|
+
if ("code" in upstream || upstream.kind === "union") {
|
|
692
|
+
return { query, targetColumns: [...targetColumns] };
|
|
693
|
+
}
|
|
694
|
+
const upstreamColumns = this.resolveTargetColumns(contextRoot, upstream.query, targetColumns.map(item => item.targetColumn));
|
|
695
|
+
if ("code" in upstreamColumns) {
|
|
696
|
+
return { query, targetColumns: [...targetColumns] };
|
|
697
|
+
}
|
|
698
|
+
const placement = this.resolveTargetPlacement(upstream.query, upstreamColumns);
|
|
699
|
+
if ("code" in placement) {
|
|
700
|
+
return { query, targetColumns: [...targetColumns] };
|
|
701
|
+
}
|
|
702
|
+
const rebasedColumns = upstreamColumns.map((item, index) => ({
|
|
703
|
+
sourceColumn: targetColumns[index].sourceColumn,
|
|
704
|
+
targetColumn: item.targetColumn
|
|
705
|
+
}));
|
|
706
|
+
return this.resolveDeepestBranchTarget(contextRoot, upstream.query, rebasedColumns, nextVisited);
|
|
707
|
+
}
|
|
588
708
|
verifyColumnResolvableInQuery(query, column) {
|
|
589
709
|
if (!query.fromClause) {
|
|
590
710
|
return {
|
|
@@ -611,8 +731,42 @@ class StaticPredicatePlacementOptimizer {
|
|
|
611
731
|
reason: `Unqualified target column '${column.column.name}' is ambiguous in a multi-source destination query.`
|
|
612
732
|
};
|
|
613
733
|
}
|
|
614
|
-
|
|
615
|
-
const
|
|
734
|
+
isGroupKeyColumn(query, column) {
|
|
735
|
+
const groupBy = query.groupByClause;
|
|
736
|
+
if (!groupBy || groupBy.mode || groupBy.grouping.length === 0) {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
return groupBy.grouping.some(grouping => {
|
|
740
|
+
const candidate = unwrapParens(grouping);
|
|
741
|
+
return candidate instanceof ValueComponent_1.ColumnReference
|
|
742
|
+
&& this.sameResolvableColumnInQuery(query, candidate, column);
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
sameResolvableColumnInQuery(query, left, right) {
|
|
746
|
+
if (sameColumnReference(left, right)) {
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
if (normalizeIdentifier(left.column.name) !== normalizeIdentifier(right.column.name)) {
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
const leftSource = this.resolveColumnSourceAlias(query, left);
|
|
753
|
+
const rightSource = this.resolveColumnSourceAlias(query, right);
|
|
754
|
+
return leftSource !== null && leftSource === rightSource;
|
|
755
|
+
}
|
|
756
|
+
resolveColumnSourceAlias(query, column) {
|
|
757
|
+
if (!query.fromClause) {
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
const bindings = this.getSourceBindings(query.fromClause);
|
|
761
|
+
const namespace = normalizeIdentifier(column.getNamespace());
|
|
762
|
+
if (namespace) {
|
|
763
|
+
const matches = bindings.filter(binding => normalizeIdentifier(binding.alias) === namespace);
|
|
764
|
+
return matches.length === 1 ? normalizeIdentifier(matches[0].alias) : null;
|
|
765
|
+
}
|
|
766
|
+
return bindings.length === 1 ? normalizeIdentifier(bindings[0].alias) : null;
|
|
767
|
+
}
|
|
768
|
+
rebasePredicate(expression, targetColumns, options) {
|
|
769
|
+
const cloned = cloneValueComponent(expression, options);
|
|
616
770
|
const visitSelect = (query, inheritedLocalAliases) => {
|
|
617
771
|
var _a, _b, _c, _d;
|
|
618
772
|
if (!(query instanceof SelectQuery_1.SimpleSelectQuery)) {
|
|
@@ -797,12 +951,103 @@ class StaticPredicatePlacementOptimizer {
|
|
|
797
951
|
return joinType.includes("left") || joinType.includes("right") || joinType.includes("full") || joinType.includes("outer");
|
|
798
952
|
});
|
|
799
953
|
}
|
|
954
|
+
hasDistinctOnBoundary(query) {
|
|
955
|
+
return query.selectClause.distinct instanceof Clause_1.DistinctOn;
|
|
956
|
+
}
|
|
957
|
+
hasOrdinaryDistinct(query) {
|
|
958
|
+
return query.selectClause.distinct !== null && !this.hasDistinctOnBoundary(query);
|
|
959
|
+
}
|
|
800
960
|
getOutputColumnMatchCount(root, binding, columnName) {
|
|
801
961
|
const target = this.resolveSourceQueryForColumns(root, binding.source);
|
|
802
|
-
if (!target
|
|
962
|
+
if (!target) {
|
|
963
|
+
return 0;
|
|
964
|
+
}
|
|
965
|
+
if (target instanceof SelectQuery_1.BinarySelectQuery) {
|
|
966
|
+
const branches = this.collectUnionBranches(target);
|
|
967
|
+
if ("code" in branches) {
|
|
968
|
+
return 0;
|
|
969
|
+
}
|
|
970
|
+
return this.collectSelectOutputs(root, branches[0]).filter(item => normalizeIdentifier(item.name) === normalizeIdentifier(columnName)).length;
|
|
971
|
+
}
|
|
972
|
+
if (!(target instanceof SelectQuery_1.SimpleSelectQuery)) {
|
|
803
973
|
return 0;
|
|
804
974
|
}
|
|
805
|
-
return target.
|
|
975
|
+
return this.collectSelectOutputs(root, target).filter(item => normalizeIdentifier(item.name) === normalizeIdentifier(columnName)).length;
|
|
976
|
+
}
|
|
977
|
+
collectUnionBranches(query) {
|
|
978
|
+
const branches = [];
|
|
979
|
+
const visit = (select) => {
|
|
980
|
+
var _a;
|
|
981
|
+
if (select instanceof SelectQuery_1.SimpleSelectQuery) {
|
|
982
|
+
branches.push(select);
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
if (!(select instanceof SelectQuery_1.BinarySelectQuery)) {
|
|
986
|
+
return {
|
|
987
|
+
code: "UNION_BOUNDARY",
|
|
988
|
+
reason: "Predicate would need distribution into a non-simple UNION branch, which is unsupported."
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
const operator = select.operator.value.trim().toLowerCase();
|
|
992
|
+
if (operator !== "union" && operator !== "union all") {
|
|
993
|
+
return {
|
|
994
|
+
code: "UNION_BOUNDARY",
|
|
995
|
+
reason: `Predicate would need distribution through '${select.operator.value}', which is unsupported.`
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
return (_a = visit(select.left)) !== null && _a !== void 0 ? _a : visit(select.right);
|
|
999
|
+
};
|
|
1000
|
+
const skip = visit(query);
|
|
1001
|
+
return skip !== null && skip !== void 0 ? skip : branches;
|
|
1002
|
+
}
|
|
1003
|
+
resolveUnionOutputIndex(root, firstBranch, outputColumnName) {
|
|
1004
|
+
const matches = [];
|
|
1005
|
+
this.collectSelectOutputs(root, firstBranch).forEach((item, index) => {
|
|
1006
|
+
if (normalizeIdentifier(item.name) === normalizeIdentifier(outputColumnName)) {
|
|
1007
|
+
matches.push(index);
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
if (matches.length !== 1) {
|
|
1011
|
+
return {
|
|
1012
|
+
code: "AMBIGUOUS_TARGET_COLUMN",
|
|
1013
|
+
reason: matches.length === 0
|
|
1014
|
+
? `UNION output does not expose '${outputColumnName}' from its first branch.`
|
|
1015
|
+
: `UNION first branch exposes multiple '${outputColumnName}' columns.`
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
return { index: matches[0] };
|
|
1019
|
+
}
|
|
1020
|
+
resolveTargetColumnByOutputIndex(root, query, outputIndex, sourceColumn) {
|
|
1021
|
+
const output = this.collectSelectOutputs(root, query)[outputIndex];
|
|
1022
|
+
if (!output) {
|
|
1023
|
+
return {
|
|
1024
|
+
code: "UNION_COLUMN_MISMATCH",
|
|
1025
|
+
reason: `UNION branch does not expose column '${sourceColumn.column.name}' at output position ${outputIndex + 1}.`
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
if (!(output.value instanceof ValueComponent_1.ColumnReference)) {
|
|
1029
|
+
return {
|
|
1030
|
+
code: "EXPRESSION_OUTPUT_UNSUPPORTED",
|
|
1031
|
+
reason: `UNION branch output at position ${outputIndex + 1} for '${sourceColumn.column.name}' is an expression, not a direct column reference.`
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
const sourceResolution = this.verifyColumnResolvableInQuery(query, output.value);
|
|
1035
|
+
if (sourceResolution) {
|
|
1036
|
+
return sourceResolution;
|
|
1037
|
+
}
|
|
1038
|
+
return {
|
|
1039
|
+
sourceColumn,
|
|
1040
|
+
targetColumn: output.value
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
collectSelectOutputs(root, query) {
|
|
1044
|
+
var _a, _b, _c, _d;
|
|
1045
|
+
const commonTables = [
|
|
1046
|
+
...((_b = (_a = query.withClause) === null || _a === void 0 ? void 0 : _a.tables) !== null && _b !== void 0 ? _b : []),
|
|
1047
|
+
...((_d = (_c = root.withClause) === null || _c === void 0 ? void 0 : _c.tables) !== null && _d !== void 0 ? _d : [])
|
|
1048
|
+
];
|
|
1049
|
+
const collector = new SelectOutputCollector_1.SelectOutputCollector(null, commonTables.length > 0 ? commonTables : null);
|
|
1050
|
+
return collector.collect(query);
|
|
806
1051
|
}
|
|
807
1052
|
resolveSourceQueryForColumns(root, source) {
|
|
808
1053
|
var _a;
|
|
@@ -817,15 +1062,6 @@ class StaticPredicatePlacementOptimizer {
|
|
|
817
1062
|
}
|
|
818
1063
|
return null;
|
|
819
1064
|
}
|
|
820
|
-
getSelectItemOutputName(item) {
|
|
821
|
-
if (item.identifier) {
|
|
822
|
-
return item.identifier.name;
|
|
823
|
-
}
|
|
824
|
-
if (item.value instanceof ValueComponent_1.ColumnReference) {
|
|
825
|
-
return item.value.column.name;
|
|
826
|
-
}
|
|
827
|
-
return null;
|
|
828
|
-
}
|
|
829
1065
|
findCte(root, name) {
|
|
830
1066
|
var _a, _b;
|
|
831
1067
|
const normalized = normalizeIdentifier(name);
|
|
@@ -1171,9 +1407,9 @@ class StaticPredicatePlacementOptimizer {
|
|
|
1171
1407
|
visit(expression);
|
|
1172
1408
|
return names;
|
|
1173
1409
|
}
|
|
1174
|
-
makeSkipped(expression, draft) {
|
|
1410
|
+
makeSkipped(expression, draft, options) {
|
|
1175
1411
|
return {
|
|
1176
|
-
predicateSql: (0, SqlComponentFormatter_1.formatSqlComponent)(expression),
|
|
1412
|
+
predicateSql: (0, SqlComponentFormatter_1.formatSqlComponent)(expression, options),
|
|
1177
1413
|
scopeId: "scope:root",
|
|
1178
1414
|
...draft
|
|
1179
1415
|
};
|
|
@@ -1182,6 +1418,7 @@ class StaticPredicatePlacementOptimizer {
|
|
|
1182
1418
|
return {
|
|
1183
1419
|
ok: params.errors.length === 0,
|
|
1184
1420
|
sql: params.sql,
|
|
1421
|
+
query: params.query,
|
|
1185
1422
|
applied: params.applied,
|
|
1186
1423
|
skipped: params.skipped,
|
|
1187
1424
|
warnings: params.warnings,
|