rawsql-ts 0.26.0 → 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 +2 -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/ConditionOptimization.d.ts +11 -1
- package/dist/esm/transformers/ConditionOptimization.js +122 -66
- 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 -141
- package/dist/esm/transformers/ParameterConditionPlacementOptimizer.js.map +1 -1
- package/dist/esm/transformers/SSSQLFilterBuilder.d.ts +6 -0
- package/dist/esm/transformers/SSSQLFilterBuilder.js +84 -8
- package/dist/esm/transformers/SSSQLFilterBuilder.js.map +1 -1
- package/dist/esm/transformers/SqlComponentFormatter.d.ts +11 -0
- package/dist/esm/transformers/SqlComponentFormatter.js +16 -0
- package/dist/esm/transformers/SqlComponentFormatter.js.map +1 -0
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.d.ts +103 -0
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.js +1435 -0
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.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 +2 -0
- package/dist/src/transformers/ConditionOptimization.d.ts +11 -1
- package/dist/src/transformers/ParameterConditionPlacementOptimizer.d.ts +26 -7
- package/dist/src/transformers/SSSQLFilterBuilder.d.ts +6 -0
- package/dist/src/transformers/SqlComponentFormatter.d.ts +11 -0
- package/dist/src/transformers/StaticPredicatePlacementOptimizer.d.ts +103 -0
- package/dist/transformers/ConditionOptimization.js +146 -65
- package/dist/transformers/ConditionOptimization.js.map +1 -1
- package/dist/transformers/ParameterConditionPlacementOptimizer.js +407 -140
- package/dist/transformers/ParameterConditionPlacementOptimizer.js.map +1 -1
- package/dist/transformers/SSSQLFilterBuilder.js +92 -16
- package/dist/transformers/SSSQLFilterBuilder.js.map +1 -1
- package/dist/transformers/SqlComponentFormatter.js +21 -0
- package/dist/transformers/SqlComponentFormatter.js.map +1 -0
- package/dist/transformers/StaticPredicatePlacementOptimizer.js +1445 -0
- package/dist/transformers/StaticPredicatePlacementOptimizer.js.map +1 -0
- package/dist/tsconfig.browser.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1435 @@
|
|
|
1
|
+
import { DistinctOn, SubQuerySource, TableSource, WhereClause } from "../models/Clause";
|
|
2
|
+
import { BinarySelectQuery, SimpleSelectQuery } from "../models/SelectQuery";
|
|
3
|
+
import { ArrayExpression, ArrayQueryExpression, BetweenExpression, BinaryExpression, CaseExpression, CastExpression, ColumnReference, FunctionCall, InlineQuery, JsonPredicateExpression, ParameterExpression, ParenExpression, TupleExpression, TypeValue, UnaryExpression, ValueList } from "../models/ValueComponent";
|
|
4
|
+
import { SelectQueryParser } from "../parsers/SelectQueryParser";
|
|
5
|
+
import { ValueParser } from "../parsers/ValueParser";
|
|
6
|
+
import { formatSqlComponent, hasSqlComponentFormatOverride } from "./SqlComponentFormatter";
|
|
7
|
+
import { SelectOutputCollector } from "./SelectOutputCollector";
|
|
8
|
+
const VOLATILE_OR_UNSUPPORTED_FUNCTION_REASON = "Predicate contains a function call; volatile and expression predicates are not moved in the safe-only implementation.";
|
|
9
|
+
const normalizeIdentifier = (value) => value.trim().toLowerCase();
|
|
10
|
+
const unwrapParens = (expression) => {
|
|
11
|
+
let candidate = expression;
|
|
12
|
+
while (candidate instanceof ParenExpression) {
|
|
13
|
+
candidate = candidate.expression;
|
|
14
|
+
}
|
|
15
|
+
return candidate;
|
|
16
|
+
};
|
|
17
|
+
const isBinaryOperator = (expression, operator) => {
|
|
18
|
+
const candidate = unwrapParens(expression);
|
|
19
|
+
return candidate instanceof BinaryExpression
|
|
20
|
+
&& candidate.operator.value.trim().toLowerCase() === operator;
|
|
21
|
+
};
|
|
22
|
+
const collectTopLevelAndTerms = (expression) => {
|
|
23
|
+
const candidate = unwrapParens(expression);
|
|
24
|
+
if (!isBinaryOperator(candidate, "and")) {
|
|
25
|
+
return [expression];
|
|
26
|
+
}
|
|
27
|
+
return [
|
|
28
|
+
...collectTopLevelAndTerms(candidate.left),
|
|
29
|
+
...collectTopLevelAndTerms(candidate.right)
|
|
30
|
+
];
|
|
31
|
+
};
|
|
32
|
+
const rebuildWhereWithoutTerms = (query, termsToRemove) => {
|
|
33
|
+
if (!query.whereClause || termsToRemove.size === 0) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const remaining = collectTopLevelAndTerms(query.whereClause.condition)
|
|
37
|
+
.filter(term => !termsToRemove.has(term));
|
|
38
|
+
if (remaining.length === 0) {
|
|
39
|
+
query.whereClause = null;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
let rebuilt = remaining[0];
|
|
43
|
+
for (let index = 1; index < remaining.length; index += 1) {
|
|
44
|
+
rebuilt = new BinaryExpression(rebuilt, "and", remaining[index]);
|
|
45
|
+
}
|
|
46
|
+
query.whereClause = new WhereClause(rebuilt);
|
|
47
|
+
};
|
|
48
|
+
const cloneValueComponent = (expression, options) => {
|
|
49
|
+
return ValueParser.parse(formatSqlComponent(expression, options));
|
|
50
|
+
};
|
|
51
|
+
const cloneColumnReference = (reference) => {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
const namespaces = (_b = (_a = reference.namespaces) === null || _a === void 0 ? void 0 : _a.map(namespace => namespace.name)) !== null && _b !== void 0 ? _b : null;
|
|
54
|
+
return new ColumnReference(namespaces, reference.column.name);
|
|
55
|
+
};
|
|
56
|
+
const columnReferenceText = (reference) => {
|
|
57
|
+
const namespace = reference.getNamespace();
|
|
58
|
+
return namespace ? `${namespace}.${reference.column.name}` : reference.column.name;
|
|
59
|
+
};
|
|
60
|
+
const sameColumnReference = (left, right) => {
|
|
61
|
+
return normalizeIdentifier(left.column.name) === normalizeIdentifier(right.column.name)
|
|
62
|
+
&& normalizeIdentifier(left.getNamespace()) === normalizeIdentifier(right.getNamespace());
|
|
63
|
+
};
|
|
64
|
+
const appendUnique = (items, value) => {
|
|
65
|
+
if (!items.includes(value)) {
|
|
66
|
+
items.push(value);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
export class StaticPredicatePlacementOptimizer {
|
|
70
|
+
plan(input, options = {}) {
|
|
71
|
+
var _a, _b, _c;
|
|
72
|
+
const parsed = this.parseInput(input, options);
|
|
73
|
+
const warnings = [...parsed.warnings];
|
|
74
|
+
const errors = [...parsed.errors];
|
|
75
|
+
if (!parsed.query) {
|
|
76
|
+
return this.buildResult({
|
|
77
|
+
query: null,
|
|
78
|
+
sql: parsed.sql,
|
|
79
|
+
applied: [],
|
|
80
|
+
skipped: [],
|
|
81
|
+
warnings,
|
|
82
|
+
errors,
|
|
83
|
+
dryRun: (_a = options.dryRun) !== null && _a !== void 0 ? _a : true,
|
|
84
|
+
formatterGeneratedSource: parsed.formatterGeneratedSource
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (!(parsed.query instanceof SimpleSelectQuery)) {
|
|
88
|
+
warnings.push({
|
|
89
|
+
code: "UNSUPPORTED_ROOT_QUERY",
|
|
90
|
+
message: "Static predicate placement currently supports only SimpleSelectQuery roots."
|
|
91
|
+
});
|
|
92
|
+
return this.buildResult({
|
|
93
|
+
query: parsed.query,
|
|
94
|
+
sql: parsed.sql,
|
|
95
|
+
applied: [],
|
|
96
|
+
skipped: [],
|
|
97
|
+
warnings,
|
|
98
|
+
errors,
|
|
99
|
+
dryRun: (_b = options.dryRun) !== null && _b !== void 0 ? _b : true,
|
|
100
|
+
formatterGeneratedSource: parsed.formatterGeneratedSource
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const query = parsed.query;
|
|
104
|
+
const applied = [];
|
|
105
|
+
const skipped = [];
|
|
106
|
+
const movedTerms = [];
|
|
107
|
+
for (const term of query.whereClause ? collectTopLevelAndTerms(query.whereClause.condition) : []) {
|
|
108
|
+
const candidate = this.analyzeCandidate(query, term, options);
|
|
109
|
+
if (!candidate) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if ("code" in candidate) {
|
|
113
|
+
skipped.push(this.makeSkipped(term, candidate, options));
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const target = this.resolveTarget(query, candidate);
|
|
117
|
+
if ("code" in target) {
|
|
118
|
+
skipped.push(this.makeSkipped(term, target, options));
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
let appliedReason = "";
|
|
122
|
+
if (target.kind === "simple") {
|
|
123
|
+
const targetColumns = this.resolveTargetColumns(query, target.query, candidate.references);
|
|
124
|
+
if ("code" in targetColumns) {
|
|
125
|
+
skipped.push(this.makeSkipped(term, targetColumns, options));
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const placement = this.resolveTargetPlacement(target.query, targetColumns);
|
|
129
|
+
if ("code" in placement) {
|
|
130
|
+
skipped.push(this.makeSkipped(term, placement, options));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const movedPredicate = this.rebasePredicate(candidate.expression, targetColumns, options);
|
|
134
|
+
target.query.appendWhere(movedPredicate);
|
|
135
|
+
appliedReason = placement.reason;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const placements = [];
|
|
139
|
+
let skip = null;
|
|
140
|
+
for (const branch of target.branches) {
|
|
141
|
+
const placement = this.resolveTargetPlacement(branch.query, branch.targetColumns);
|
|
142
|
+
if ("code" in placement) {
|
|
143
|
+
skip = placement;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
placements.push(placement);
|
|
147
|
+
}
|
|
148
|
+
if (skip) {
|
|
149
|
+
skipped.push(this.makeSkipped(term, skip, options));
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
for (const branch of target.branches) {
|
|
153
|
+
const movedPredicate = this.rebasePredicate(candidate.expression, branch.targetColumns, options);
|
|
154
|
+
branch.query.appendWhere(movedPredicate);
|
|
155
|
+
}
|
|
156
|
+
appliedReason = placements.some(item => /group by/i.test(item.reason))
|
|
157
|
+
? "Predicate is distributed to every UNION branch by output column position; grouped branches only receive GROUP BY-key predicates."
|
|
158
|
+
: "Predicate is distributed to every UNION branch by output column position before unsafe query boundaries.";
|
|
159
|
+
}
|
|
160
|
+
movedTerms.push(term);
|
|
161
|
+
applied.push({
|
|
162
|
+
kind: "move_static_predicate",
|
|
163
|
+
predicateSql: candidate.predicateSql,
|
|
164
|
+
fromScopeId: "scope:root",
|
|
165
|
+
toScopeId: target.scopeId,
|
|
166
|
+
reason: appliedReason,
|
|
167
|
+
columnReferences: candidate.references.map(columnReferenceText)
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
rebuildWhereWithoutTerms(query, new Set(movedTerms));
|
|
171
|
+
const sql = applied.length > 0 || hasSqlComponentFormatOverride(options)
|
|
172
|
+
? formatSqlComponent(query, options)
|
|
173
|
+
: parsed.sql;
|
|
174
|
+
return this.buildResult({
|
|
175
|
+
query,
|
|
176
|
+
sql,
|
|
177
|
+
applied,
|
|
178
|
+
skipped,
|
|
179
|
+
warnings,
|
|
180
|
+
errors,
|
|
181
|
+
dryRun: (_c = options.dryRun) !== null && _c !== void 0 ? _c : true,
|
|
182
|
+
formatterGeneratedSource: parsed.formatterGeneratedSource
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
optimize(input, options = {}) {
|
|
186
|
+
var _a;
|
|
187
|
+
return this.plan(input, Object.assign(Object.assign({}, options), { dryRun: (_a = options.dryRun) !== null && _a !== void 0 ? _a : false }));
|
|
188
|
+
}
|
|
189
|
+
parseInput(input, options) {
|
|
190
|
+
const warnings = [];
|
|
191
|
+
const errors = [];
|
|
192
|
+
try {
|
|
193
|
+
const sourceSql = typeof input === "string"
|
|
194
|
+
? input
|
|
195
|
+
: formatSqlComponent(input, options);
|
|
196
|
+
if (typeof input !== "string" && options.cloneInput === false) {
|
|
197
|
+
return {
|
|
198
|
+
query: input,
|
|
199
|
+
sql: sourceSql,
|
|
200
|
+
formatterGeneratedSource: false,
|
|
201
|
+
warnings,
|
|
202
|
+
errors
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (typeof input !== "string") {
|
|
206
|
+
warnings.push({
|
|
207
|
+
code: "AST_INPUT_FORMATTED",
|
|
208
|
+
message: "AST input is cloned through formatter output so the caller-owned query is not mutated."
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
query: SelectQueryParser.parse(sourceSql),
|
|
213
|
+
sql: sourceSql,
|
|
214
|
+
formatterGeneratedSource: typeof input !== "string",
|
|
215
|
+
warnings,
|
|
216
|
+
errors
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
221
|
+
errors.push({
|
|
222
|
+
code: "PARSE_FAILED",
|
|
223
|
+
message: "Static predicate placement could not parse the input SQL.",
|
|
224
|
+
detail
|
|
225
|
+
});
|
|
226
|
+
return {
|
|
227
|
+
query: null,
|
|
228
|
+
sql: typeof input === "string" ? input : "",
|
|
229
|
+
formatterGeneratedSource: typeof input !== "string",
|
|
230
|
+
warnings,
|
|
231
|
+
errors
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
analyzeCandidate(root, expression, options) {
|
|
236
|
+
if (this.collectParameterNames(expression).length > 0) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const unsupported = this.findUnsupportedExpression(expression, this.isExistsPredicate(expression));
|
|
240
|
+
if (unsupported) {
|
|
241
|
+
return unsupported;
|
|
242
|
+
}
|
|
243
|
+
const references = this.collectRootColumnReferences(root, expression);
|
|
244
|
+
if (references.length === 0) {
|
|
245
|
+
return {
|
|
246
|
+
code: "NO_COLUMN_REFERENCE",
|
|
247
|
+
reason: "Static predicate has no outer column reference to anchor the move."
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
expression,
|
|
252
|
+
predicateSql: formatSqlComponent(expression, options),
|
|
253
|
+
references
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
isExistsPredicate(expression) {
|
|
257
|
+
const candidate = unwrapParens(expression);
|
|
258
|
+
if (!(candidate instanceof UnaryExpression)) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
const operator = candidate.operator.value.trim().toLowerCase();
|
|
262
|
+
return operator === "exists" && unwrapParens(candidate.expression) instanceof InlineQuery;
|
|
263
|
+
}
|
|
264
|
+
findUnsupportedExpression(expression, allowExistsSubquery) {
|
|
265
|
+
let found = null;
|
|
266
|
+
const visitSelect = (query) => {
|
|
267
|
+
var _a, _b, _c, _d;
|
|
268
|
+
if (found) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (query instanceof BinarySelectQuery) {
|
|
272
|
+
found = {
|
|
273
|
+
code: "UNION_BOUNDARY",
|
|
274
|
+
reason: "Static predicate would need distribution into a UNION branch, which is unsupported."
|
|
275
|
+
};
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (!(query instanceof SimpleSelectQuery)) {
|
|
279
|
+
found = {
|
|
280
|
+
code: "UNSUPPORTED_SUBQUERY",
|
|
281
|
+
reason: "Static predicate contains a non-simple subquery, which is unsupported."
|
|
282
|
+
};
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
query.selectClause.items.forEach(item => visit(item.value));
|
|
286
|
+
for (const join of (_b = (_a = query.fromClause) === null || _a === void 0 ? void 0 : _a.joins) !== null && _b !== void 0 ? _b : []) {
|
|
287
|
+
if (join.condition) {
|
|
288
|
+
visit(join.condition.condition);
|
|
289
|
+
}
|
|
290
|
+
const source = join.source.datasource;
|
|
291
|
+
if (source instanceof SubQuerySource) {
|
|
292
|
+
visitSelect(source.query);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (query.whereClause) {
|
|
296
|
+
visit(query.whereClause.condition);
|
|
297
|
+
}
|
|
298
|
+
if (query.havingClause) {
|
|
299
|
+
visit(query.havingClause.condition);
|
|
300
|
+
}
|
|
301
|
+
for (const cte of (_d = (_c = query.withClause) === null || _c === void 0 ? void 0 : _c.tables) !== null && _d !== void 0 ? _d : []) {
|
|
302
|
+
if (cte.query instanceof SimpleSelectQuery || cte.query instanceof BinarySelectQuery) {
|
|
303
|
+
visitSelect(cte.query);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
const visit = (value) => {
|
|
308
|
+
if (found) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const candidate = unwrapParens(value);
|
|
312
|
+
if (candidate instanceof BinaryExpression) {
|
|
313
|
+
visit(candidate.left);
|
|
314
|
+
visit(candidate.right);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (candidate instanceof FunctionCall) {
|
|
318
|
+
found = {
|
|
319
|
+
code: "FUNCTION_PREDICATE_UNSUPPORTED",
|
|
320
|
+
reason: VOLATILE_OR_UNSUPPORTED_FUNCTION_REASON
|
|
321
|
+
};
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (candidate instanceof CaseExpression) {
|
|
325
|
+
found = {
|
|
326
|
+
code: "CASE_PREDICATE_UNSUPPORTED",
|
|
327
|
+
reason: "CASE predicates are not moved in the first safe-only implementation."
|
|
328
|
+
};
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (candidate instanceof InlineQuery) {
|
|
332
|
+
if (!allowExistsSubquery) {
|
|
333
|
+
found = {
|
|
334
|
+
code: "SUBQUERY_PREDICATE_UNSUPPORTED",
|
|
335
|
+
reason: "Subquery predicates are only moved when they are simple EXISTS predicates."
|
|
336
|
+
};
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
visitSelect(candidate.selectQuery);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (candidate instanceof ArrayQueryExpression) {
|
|
343
|
+
found = {
|
|
344
|
+
code: "SUBQUERY_PREDICATE_UNSUPPORTED",
|
|
345
|
+
reason: "Array subquery predicates are not moved in the first safe-only implementation."
|
|
346
|
+
};
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (candidate instanceof UnaryExpression) {
|
|
350
|
+
visit(candidate.expression);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (candidate instanceof CastExpression) {
|
|
354
|
+
visit(candidate.input);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (candidate instanceof JsonPredicateExpression) {
|
|
358
|
+
visit(candidate.expression);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (candidate instanceof ArrayExpression) {
|
|
362
|
+
visit(candidate.expression);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (candidate instanceof ValueList) {
|
|
366
|
+
candidate.values.forEach(visit);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (candidate instanceof TupleExpression) {
|
|
370
|
+
candidate.values.forEach(visit);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (candidate instanceof TypeValue && candidate.argument) {
|
|
374
|
+
visit(candidate.argument);
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
visit(expression);
|
|
378
|
+
return found;
|
|
379
|
+
}
|
|
380
|
+
resolveTarget(root, candidate) {
|
|
381
|
+
const boundary = this.findRootQueryBoundary(root);
|
|
382
|
+
if (boundary) {
|
|
383
|
+
return boundary;
|
|
384
|
+
}
|
|
385
|
+
if (!root.fromClause) {
|
|
386
|
+
return {
|
|
387
|
+
code: "NO_FROM_CLAUSE",
|
|
388
|
+
reason: "Predicate has no FROM source that can receive it safely."
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const bindings = [];
|
|
392
|
+
for (const reference of candidate.references) {
|
|
393
|
+
const binding = this.resolveSourceBinding(root, root, reference);
|
|
394
|
+
if ("code" in binding) {
|
|
395
|
+
return binding;
|
|
396
|
+
}
|
|
397
|
+
if (!bindings.some(item => item.source === binding.source)) {
|
|
398
|
+
bindings.push(binding);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (bindings.length !== 1) {
|
|
402
|
+
return {
|
|
403
|
+
code: "MULTIPLE_SOURCE_REFERENCES",
|
|
404
|
+
reason: "Static predicate references multiple source query blocks; moving it may change semantics."
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const binding = bindings[0];
|
|
408
|
+
const nullableSide = this.findNullableSideBoundary(root.fromClause, binding);
|
|
409
|
+
if (nullableSide) {
|
|
410
|
+
return nullableSide;
|
|
411
|
+
}
|
|
412
|
+
const upstream = this.resolveUpstreamQuery(root, binding, candidate.references);
|
|
413
|
+
if ("code" in upstream) {
|
|
414
|
+
return upstream;
|
|
415
|
+
}
|
|
416
|
+
return upstream;
|
|
417
|
+
}
|
|
418
|
+
findRootQueryBoundary(query) {
|
|
419
|
+
if (this.hasDistinctOnBoundary(query)) {
|
|
420
|
+
return {
|
|
421
|
+
code: "DISTINCT_BOUNDARY",
|
|
422
|
+
reason: "Predicate crosses DISTINCT ON boundary; moving it may change semantics."
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
if (this.hasWindowUsage(query)) {
|
|
426
|
+
return {
|
|
427
|
+
code: "WINDOW_BOUNDARY",
|
|
428
|
+
reason: "Predicate crosses WINDOW boundary; moving it may change semantics."
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
resolveTargetPlacement(query, targetColumns) {
|
|
434
|
+
const hasOrdinaryDistinct = this.hasOrdinaryDistinct(query);
|
|
435
|
+
if (this.hasDistinctOnBoundary(query)) {
|
|
436
|
+
return {
|
|
437
|
+
code: "DISTINCT_BOUNDARY",
|
|
438
|
+
reason: "Predicate crosses DISTINCT ON boundary; moving it may change semantics."
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
if (this.hasWindowUsage(query)) {
|
|
442
|
+
return {
|
|
443
|
+
code: "WINDOW_BOUNDARY",
|
|
444
|
+
reason: "Predicate crosses WINDOW boundary; moving it may change semantics."
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
if (query.limitClause || query.offsetClause || query.fetchClause) {
|
|
448
|
+
return {
|
|
449
|
+
code: "ROW_LIMIT_BOUNDARY",
|
|
450
|
+
reason: "Predicate crosses LIMIT/OFFSET/FETCH boundary; moving it may change row selection semantics."
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
if (query.fromClause && this.hasOuterJoin(query.fromClause)) {
|
|
454
|
+
return {
|
|
455
|
+
code: "OUTER_JOIN_BOUNDARY",
|
|
456
|
+
reason: "Target query contains an OUTER JOIN boundary that is not moved across in the safe-only implementation."
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
if (query.groupByClause) {
|
|
460
|
+
const allReferencesAreGroupKeys = targetColumns.every(item => this.isGroupKeyColumn(query, item.targetColumn));
|
|
461
|
+
if (!allReferencesAreGroupKeys) {
|
|
462
|
+
return {
|
|
463
|
+
code: "GROUP_BY_BOUNDARY",
|
|
464
|
+
reason: "Predicate references a target column that is not proven to be a GROUP BY key."
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
reason: "Predicate references only GROUP BY keys; it is moved into pre-aggregation WHERE."
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
if (query.havingClause) {
|
|
472
|
+
return {
|
|
473
|
+
code: "GROUP_BY_BOUNDARY",
|
|
474
|
+
reason: "Predicate crosses HAVING aggregation boundary; moving it may change semantics."
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
if (hasOrdinaryDistinct) {
|
|
478
|
+
return {
|
|
479
|
+
reason: "Predicate references direct ordinary DISTINCT output columns; it is moved into the DISTINCT input WHERE."
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
reason: "All outer references resolve to direct upstream outputs before unsafe query boundaries."
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
resolveSourceBinding(contextRoot, query, column) {
|
|
487
|
+
const fromClause = query.fromClause;
|
|
488
|
+
if (!fromClause) {
|
|
489
|
+
return {
|
|
490
|
+
code: "NO_FROM_CLAUSE",
|
|
491
|
+
reason: "Predicate has no FROM source that can receive it safely."
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
const bindings = this.getSourceBindings(fromClause);
|
|
495
|
+
const namespace = normalizeIdentifier(column.getNamespace());
|
|
496
|
+
const columnName = column.column.name;
|
|
497
|
+
if (namespace) {
|
|
498
|
+
const matches = bindings.filter(binding => normalizeIdentifier(binding.alias) === namespace);
|
|
499
|
+
if (matches.length !== 1) {
|
|
500
|
+
return {
|
|
501
|
+
code: "AMBIGUOUS_COLUMN_SOURCE",
|
|
502
|
+
reason: `Column source alias '${column.getNamespace()}' is not uniquely resolvable.`
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
const matchCount = this.getOutputColumnMatchCount(contextRoot, matches[0], columnName);
|
|
506
|
+
if (matchCount === 0) {
|
|
507
|
+
return {
|
|
508
|
+
code: "COLUMN_NOT_AVAILABLE_UPSTREAM",
|
|
509
|
+
reason: `Column '${columnReferenceText(column)}' is not a direct output of the referenced source.`
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
if (matchCount > 1) {
|
|
513
|
+
return {
|
|
514
|
+
code: "AMBIGUOUS_COLUMN_REFERENCE",
|
|
515
|
+
reason: `Column '${columnReferenceText(column)}' resolves to multiple outputs in the referenced source.`
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
return matches[0];
|
|
519
|
+
}
|
|
520
|
+
const matches = [];
|
|
521
|
+
for (const binding of bindings) {
|
|
522
|
+
const matchCount = this.getOutputColumnMatchCount(contextRoot, binding, columnName);
|
|
523
|
+
if (matchCount > 1) {
|
|
524
|
+
return {
|
|
525
|
+
code: "AMBIGUOUS_COLUMN_REFERENCE",
|
|
526
|
+
reason: `Column '${columnName}' resolves to multiple outputs in source '${binding.alias}'.`
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
if (matchCount === 1) {
|
|
530
|
+
matches.push(binding);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (matches.length !== 1) {
|
|
534
|
+
return {
|
|
535
|
+
code: "AMBIGUOUS_COLUMN_REFERENCE",
|
|
536
|
+
reason: matches.length === 0
|
|
537
|
+
? `Column '${columnName}' is not uniquely resolvable to a safe upstream source.`
|
|
538
|
+
: `Column '${columnName}' is ambiguous across source query blocks.`
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
return matches[0];
|
|
542
|
+
}
|
|
543
|
+
resolveUpstreamQuery(root, binding, references) {
|
|
544
|
+
const source = binding.source.datasource;
|
|
545
|
+
if (source instanceof SubQuerySource) {
|
|
546
|
+
if (source.query instanceof SimpleSelectQuery) {
|
|
547
|
+
return {
|
|
548
|
+
kind: "simple",
|
|
549
|
+
query: source.query,
|
|
550
|
+
scopeId: `subquery:${binding.alias}`,
|
|
551
|
+
sourceBinding: binding
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
if (source.query instanceof BinarySelectQuery) {
|
|
555
|
+
return this.resolveUnionTarget(root, source.query, `subquery:${binding.alias}`, references);
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
code: "UNION_BOUNDARY",
|
|
559
|
+
reason: "Predicate would need distribution into a UNION or non-simple subquery, which is unsupported."
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
if (!(source instanceof TableSource)) {
|
|
563
|
+
return {
|
|
564
|
+
code: "UNSUPPORTED_SOURCE",
|
|
565
|
+
reason: "Only CTE and simple derived-table sources can receive moved static predicates."
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
const cteName = source.table.name;
|
|
569
|
+
const commonTable = this.findCte(root, cteName);
|
|
570
|
+
if (!commonTable) {
|
|
571
|
+
return {
|
|
572
|
+
code: "NO_SAFE_UPSTREAM_QUERY",
|
|
573
|
+
reason: "The referenced source is a base table, so there is no upstream query block to move into."
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
const referenceCount = this.countTableSourceReferences(root, cteName);
|
|
577
|
+
if (referenceCount !== 1) {
|
|
578
|
+
return {
|
|
579
|
+
code: "CTE_REUSE_UNSUPPORTED",
|
|
580
|
+
reason: `CTE '${cteName}' is referenced ${referenceCount} times; moving a predicate into it may affect other consumers.`
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
if (commonTable.query instanceof BinarySelectQuery) {
|
|
584
|
+
return this.resolveUnionTarget(root, commonTable.query, `cte:${commonTable.getSourceAliasName()}`, references);
|
|
585
|
+
}
|
|
586
|
+
if (!(commonTable.query instanceof SimpleSelectQuery)) {
|
|
587
|
+
return {
|
|
588
|
+
code: "UNSUPPORTED_CTE_QUERY",
|
|
589
|
+
reason: "Writable or non-select CTE bodies are not moved into by static predicate placement."
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
return {
|
|
593
|
+
kind: "simple",
|
|
594
|
+
query: commonTable.query,
|
|
595
|
+
scopeId: `cte:${commonTable.getSourceAliasName()}`,
|
|
596
|
+
sourceBinding: binding,
|
|
597
|
+
cteName: commonTable.getSourceAliasName()
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
resolveUnionTarget(root, query, scopeId, references) {
|
|
601
|
+
const branches = this.collectUnionBranches(query);
|
|
602
|
+
if ("code" in branches) {
|
|
603
|
+
return branches;
|
|
604
|
+
}
|
|
605
|
+
const outputIndexes = new Map();
|
|
606
|
+
for (const reference of references) {
|
|
607
|
+
const outputIndex = this.resolveUnionOutputIndex(root, branches[0], reference.column.name);
|
|
608
|
+
if ("code" in outputIndex) {
|
|
609
|
+
return outputIndex;
|
|
610
|
+
}
|
|
611
|
+
outputIndexes.set(reference, outputIndex.index);
|
|
612
|
+
}
|
|
613
|
+
const branchTargets = [];
|
|
614
|
+
for (const branch of branches) {
|
|
615
|
+
const targetColumns = [];
|
|
616
|
+
for (const [reference, outputIndex] of outputIndexes.entries()) {
|
|
617
|
+
const targetColumn = this.resolveTargetColumnByOutputIndex(root, branch, outputIndex, reference);
|
|
618
|
+
if ("code" in targetColumn) {
|
|
619
|
+
return targetColumn;
|
|
620
|
+
}
|
|
621
|
+
targetColumns.push(targetColumn);
|
|
622
|
+
}
|
|
623
|
+
branchTargets.push(this.resolveDeepestBranchTarget(root, branch, targetColumns));
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
kind: "union",
|
|
627
|
+
scopeId,
|
|
628
|
+
branches: branchTargets
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
resolveTargetColumns(root, query, references) {
|
|
632
|
+
const resolved = [];
|
|
633
|
+
for (const reference of references) {
|
|
634
|
+
const matches = this.collectSelectOutputs(root, query).filter(item => normalizeIdentifier(item.name) === normalizeIdentifier(reference.column.name));
|
|
635
|
+
if (matches.length !== 1) {
|
|
636
|
+
return {
|
|
637
|
+
code: "AMBIGUOUS_TARGET_COLUMN",
|
|
638
|
+
reason: matches.length === 0
|
|
639
|
+
? `Target query does not expose '${reference.column.name}' as a direct output column.`
|
|
640
|
+
: `Target query exposes multiple '${reference.column.name}' columns.`
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
const value = matches[0].value;
|
|
644
|
+
if (!(value instanceof ColumnReference)) {
|
|
645
|
+
return {
|
|
646
|
+
code: "EXPRESSION_OUTPUT_UNSUPPORTED",
|
|
647
|
+
reason: `Target output '${reference.column.name}' is an expression, not a direct column reference.`
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
const sourceResolution = this.verifyColumnResolvableInQuery(query, value);
|
|
651
|
+
if (sourceResolution) {
|
|
652
|
+
return sourceResolution;
|
|
653
|
+
}
|
|
654
|
+
resolved.push({
|
|
655
|
+
sourceColumn: reference,
|
|
656
|
+
targetColumn: value
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
return resolved;
|
|
660
|
+
}
|
|
661
|
+
resolveDeepestBranchTarget(contextRoot, query, targetColumns, visited = new Set()) {
|
|
662
|
+
if (visited.has(query) || targetColumns.length === 0) {
|
|
663
|
+
return { query, targetColumns: [...targetColumns] };
|
|
664
|
+
}
|
|
665
|
+
const nextVisited = new Set(visited);
|
|
666
|
+
nextVisited.add(query);
|
|
667
|
+
const bindings = [];
|
|
668
|
+
for (const item of targetColumns) {
|
|
669
|
+
const binding = this.resolveSourceBinding(contextRoot, query, item.targetColumn);
|
|
670
|
+
if ("code" in binding) {
|
|
671
|
+
return { query, targetColumns: [...targetColumns] };
|
|
672
|
+
}
|
|
673
|
+
if (!bindings.some(existing => existing.source === binding.source)) {
|
|
674
|
+
bindings.push(binding);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (bindings.length !== 1) {
|
|
678
|
+
return { query, targetColumns: [...targetColumns] };
|
|
679
|
+
}
|
|
680
|
+
const binding = bindings[0];
|
|
681
|
+
const nullableSide = query.fromClause
|
|
682
|
+
? this.findNullableSideBoundary(query.fromClause, binding)
|
|
683
|
+
: null;
|
|
684
|
+
if (nullableSide) {
|
|
685
|
+
return { query, targetColumns: [...targetColumns] };
|
|
686
|
+
}
|
|
687
|
+
const upstream = this.resolveUpstreamQuery(contextRoot, binding, targetColumns.map(item => item.targetColumn));
|
|
688
|
+
if ("code" in upstream || upstream.kind === "union") {
|
|
689
|
+
return { query, targetColumns: [...targetColumns] };
|
|
690
|
+
}
|
|
691
|
+
const upstreamColumns = this.resolveTargetColumns(contextRoot, upstream.query, targetColumns.map(item => item.targetColumn));
|
|
692
|
+
if ("code" in upstreamColumns) {
|
|
693
|
+
return { query, targetColumns: [...targetColumns] };
|
|
694
|
+
}
|
|
695
|
+
const placement = this.resolveTargetPlacement(upstream.query, upstreamColumns);
|
|
696
|
+
if ("code" in placement) {
|
|
697
|
+
return { query, targetColumns: [...targetColumns] };
|
|
698
|
+
}
|
|
699
|
+
const rebasedColumns = upstreamColumns.map((item, index) => ({
|
|
700
|
+
sourceColumn: targetColumns[index].sourceColumn,
|
|
701
|
+
targetColumn: item.targetColumn
|
|
702
|
+
}));
|
|
703
|
+
return this.resolveDeepestBranchTarget(contextRoot, upstream.query, rebasedColumns, nextVisited);
|
|
704
|
+
}
|
|
705
|
+
verifyColumnResolvableInQuery(query, column) {
|
|
706
|
+
if (!query.fromClause) {
|
|
707
|
+
return {
|
|
708
|
+
code: "NO_TARGET_FROM_CLAUSE",
|
|
709
|
+
reason: "Target query has no FROM clause where the moved column can be resolved."
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
const bindings = this.getSourceBindings(query.fromClause);
|
|
713
|
+
const namespace = normalizeIdentifier(column.getNamespace());
|
|
714
|
+
if (namespace) {
|
|
715
|
+
const matches = bindings.filter(binding => normalizeIdentifier(binding.alias) === namespace);
|
|
716
|
+
return matches.length === 1
|
|
717
|
+
? null
|
|
718
|
+
: {
|
|
719
|
+
code: "AMBIGUOUS_TARGET_COLUMN",
|
|
720
|
+
reason: `Target column '${columnReferenceText(column)}' is not uniquely resolvable in the destination query.`
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
if (bindings.length === 1) {
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
return {
|
|
727
|
+
code: "AMBIGUOUS_TARGET_COLUMN",
|
|
728
|
+
reason: `Unqualified target column '${column.column.name}' is ambiguous in a multi-source destination query.`
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
isGroupKeyColumn(query, column) {
|
|
732
|
+
const groupBy = query.groupByClause;
|
|
733
|
+
if (!groupBy || groupBy.mode || groupBy.grouping.length === 0) {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
return groupBy.grouping.some(grouping => {
|
|
737
|
+
const candidate = unwrapParens(grouping);
|
|
738
|
+
return candidate instanceof ColumnReference
|
|
739
|
+
&& this.sameResolvableColumnInQuery(query, candidate, column);
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
sameResolvableColumnInQuery(query, left, right) {
|
|
743
|
+
if (sameColumnReference(left, right)) {
|
|
744
|
+
return true;
|
|
745
|
+
}
|
|
746
|
+
if (normalizeIdentifier(left.column.name) !== normalizeIdentifier(right.column.name)) {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
const leftSource = this.resolveColumnSourceAlias(query, left);
|
|
750
|
+
const rightSource = this.resolveColumnSourceAlias(query, right);
|
|
751
|
+
return leftSource !== null && leftSource === rightSource;
|
|
752
|
+
}
|
|
753
|
+
resolveColumnSourceAlias(query, column) {
|
|
754
|
+
if (!query.fromClause) {
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
const bindings = this.getSourceBindings(query.fromClause);
|
|
758
|
+
const namespace = normalizeIdentifier(column.getNamespace());
|
|
759
|
+
if (namespace) {
|
|
760
|
+
const matches = bindings.filter(binding => normalizeIdentifier(binding.alias) === namespace);
|
|
761
|
+
return matches.length === 1 ? normalizeIdentifier(matches[0].alias) : null;
|
|
762
|
+
}
|
|
763
|
+
return bindings.length === 1 ? normalizeIdentifier(bindings[0].alias) : null;
|
|
764
|
+
}
|
|
765
|
+
rebasePredicate(expression, targetColumns, options) {
|
|
766
|
+
const cloned = cloneValueComponent(expression, options);
|
|
767
|
+
const visitSelect = (query, inheritedLocalAliases) => {
|
|
768
|
+
var _a, _b, _c, _d;
|
|
769
|
+
if (!(query instanceof SimpleSelectQuery)) {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
const localAliases = new Set(inheritedLocalAliases);
|
|
773
|
+
for (const alias of this.collectSourceAliases(query)) {
|
|
774
|
+
localAliases.add(alias);
|
|
775
|
+
}
|
|
776
|
+
query.selectClause.items.forEach(item => visit(item.value, localAliases));
|
|
777
|
+
for (const join of (_b = (_a = query.fromClause) === null || _a === void 0 ? void 0 : _a.joins) !== null && _b !== void 0 ? _b : []) {
|
|
778
|
+
if (join.condition) {
|
|
779
|
+
visit(join.condition.condition, localAliases);
|
|
780
|
+
}
|
|
781
|
+
const source = join.source.datasource;
|
|
782
|
+
if (source instanceof SubQuerySource) {
|
|
783
|
+
visitSelect(source.query, localAliases);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (query.whereClause) {
|
|
787
|
+
visit(query.whereClause.condition, localAliases);
|
|
788
|
+
}
|
|
789
|
+
if (query.havingClause) {
|
|
790
|
+
visit(query.havingClause.condition, localAliases);
|
|
791
|
+
}
|
|
792
|
+
for (const cte of (_d = (_c = query.withClause) === null || _c === void 0 ? void 0 : _c.tables) !== null && _d !== void 0 ? _d : []) {
|
|
793
|
+
if (cte.query instanceof SimpleSelectQuery || cte.query instanceof BinarySelectQuery) {
|
|
794
|
+
visitSelect(cte.query, localAliases);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
const visit = (value, localAliases) => {
|
|
799
|
+
const candidate = unwrapParens(value);
|
|
800
|
+
if (candidate instanceof ColumnReference) {
|
|
801
|
+
const namespace = normalizeIdentifier(candidate.getNamespace());
|
|
802
|
+
if (namespace && localAliases.has(namespace)) {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
const target = targetColumns.find(item => sameColumnReference(candidate, item.sourceColumn));
|
|
806
|
+
if (target) {
|
|
807
|
+
candidate.qualifiedName = cloneColumnReference(target.targetColumn).qualifiedName;
|
|
808
|
+
}
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (candidate instanceof BinaryExpression) {
|
|
812
|
+
visit(candidate.left, localAliases);
|
|
813
|
+
visit(candidate.right, localAliases);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (candidate instanceof UnaryExpression) {
|
|
817
|
+
visit(candidate.expression, localAliases);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if (candidate instanceof InlineQuery) {
|
|
821
|
+
visitSelect(candidate.selectQuery, localAliases);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
if (candidate instanceof FunctionCall) {
|
|
825
|
+
if (candidate.argument) {
|
|
826
|
+
visit(candidate.argument, localAliases);
|
|
827
|
+
}
|
|
828
|
+
if (candidate.filterCondition) {
|
|
829
|
+
visit(candidate.filterCondition, localAliases);
|
|
830
|
+
}
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (candidate instanceof CastExpression) {
|
|
834
|
+
visit(candidate.input, localAliases);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (candidate instanceof CaseExpression) {
|
|
838
|
+
if (candidate.condition) {
|
|
839
|
+
visit(candidate.condition, localAliases);
|
|
840
|
+
}
|
|
841
|
+
for (const pair of candidate.switchCase.cases) {
|
|
842
|
+
visit(pair.key, localAliases);
|
|
843
|
+
visit(pair.value, localAliases);
|
|
844
|
+
}
|
|
845
|
+
if (candidate.switchCase.elseValue) {
|
|
846
|
+
visit(candidate.switchCase.elseValue, localAliases);
|
|
847
|
+
}
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (candidate instanceof BetweenExpression) {
|
|
851
|
+
visit(candidate.expression, localAliases);
|
|
852
|
+
visit(candidate.lower, localAliases);
|
|
853
|
+
visit(candidate.upper, localAliases);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if (candidate instanceof JsonPredicateExpression) {
|
|
857
|
+
visit(candidate.expression, localAliases);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
if (candidate instanceof ArrayExpression) {
|
|
861
|
+
visit(candidate.expression, localAliases);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
if (candidate instanceof ValueList) {
|
|
865
|
+
candidate.values.forEach(item => visit(item, localAliases));
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
if (candidate instanceof TupleExpression) {
|
|
869
|
+
candidate.values.forEach(item => visit(item, localAliases));
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (candidate instanceof TypeValue && candidate.argument) {
|
|
873
|
+
visit(candidate.argument, localAliases);
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
visit(cloned, new Set());
|
|
877
|
+
return cloned;
|
|
878
|
+
}
|
|
879
|
+
getSourceBindings(fromClause) {
|
|
880
|
+
var _a, _b, _c;
|
|
881
|
+
const bindings = [{
|
|
882
|
+
source: fromClause.source,
|
|
883
|
+
alias: (_a = fromClause.source.getAliasName()) !== null && _a !== void 0 ? _a : "",
|
|
884
|
+
join: null,
|
|
885
|
+
joinIndex: -1,
|
|
886
|
+
isPrimary: true
|
|
887
|
+
}];
|
|
888
|
+
for (let index = 0; index < ((_b = fromClause.joins) !== null && _b !== void 0 ? _b : []).length; index += 1) {
|
|
889
|
+
const join = fromClause.joins[index];
|
|
890
|
+
bindings.push({
|
|
891
|
+
source: join.source,
|
|
892
|
+
alias: (_c = join.source.getAliasName()) !== null && _c !== void 0 ? _c : "",
|
|
893
|
+
join,
|
|
894
|
+
joinIndex: index,
|
|
895
|
+
isPrimary: false
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
return bindings;
|
|
899
|
+
}
|
|
900
|
+
findNullableSideBoundary(fromClause, binding) {
|
|
901
|
+
var _a;
|
|
902
|
+
const joins = (_a = fromClause.joins) !== null && _a !== void 0 ? _a : [];
|
|
903
|
+
if (binding.isPrimary) {
|
|
904
|
+
return this.hasLaterJoinThatNullsPriorSources(joins, -1)
|
|
905
|
+
? {
|
|
906
|
+
code: "OUTER_JOIN_NULLABLE_SIDE",
|
|
907
|
+
reason: "Predicate crosses OUTER JOIN nullable side; moving it may change semantics."
|
|
908
|
+
}
|
|
909
|
+
: null;
|
|
910
|
+
}
|
|
911
|
+
const join = binding.join;
|
|
912
|
+
if (!join) {
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
const joinType = join.joinType.value.toLowerCase();
|
|
916
|
+
if (joinType.includes("left")) {
|
|
917
|
+
return {
|
|
918
|
+
code: "OUTER_JOIN_NULLABLE_SIDE",
|
|
919
|
+
reason: "Predicate crosses LEFT JOIN nullable side; moving it may change semantics."
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
if (joinType.includes("full")) {
|
|
923
|
+
return {
|
|
924
|
+
code: "OUTER_JOIN_NULLABLE_SIDE",
|
|
925
|
+
reason: "Predicate crosses FULL JOIN nullable side; moving it may change semantics."
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
if (this.hasLaterJoinThatNullsPriorSources(joins, binding.joinIndex)) {
|
|
929
|
+
return {
|
|
930
|
+
code: "OUTER_JOIN_NULLABLE_SIDE",
|
|
931
|
+
reason: "Predicate crosses later RIGHT/FULL JOIN nullable side; moving it may change semantics."
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
hasLaterJoinThatNullsPriorSources(joins, sourceJoinIndex) {
|
|
937
|
+
return joins
|
|
938
|
+
.slice(sourceJoinIndex + 1)
|
|
939
|
+
.some(join => {
|
|
940
|
+
const joinType = join.joinType.value.toLowerCase();
|
|
941
|
+
return joinType.includes("right") || joinType.includes("full");
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
hasOuterJoin(fromClause) {
|
|
945
|
+
var _a;
|
|
946
|
+
return ((_a = fromClause.joins) !== null && _a !== void 0 ? _a : []).some(join => {
|
|
947
|
+
const joinType = join.joinType.value.toLowerCase();
|
|
948
|
+
return joinType.includes("left") || joinType.includes("right") || joinType.includes("full") || joinType.includes("outer");
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
hasDistinctOnBoundary(query) {
|
|
952
|
+
return query.selectClause.distinct instanceof DistinctOn;
|
|
953
|
+
}
|
|
954
|
+
hasOrdinaryDistinct(query) {
|
|
955
|
+
return query.selectClause.distinct !== null && !this.hasDistinctOnBoundary(query);
|
|
956
|
+
}
|
|
957
|
+
getOutputColumnMatchCount(root, binding, columnName) {
|
|
958
|
+
const target = this.resolveSourceQueryForColumns(root, binding.source);
|
|
959
|
+
if (!target) {
|
|
960
|
+
return 0;
|
|
961
|
+
}
|
|
962
|
+
if (target instanceof BinarySelectQuery) {
|
|
963
|
+
const branches = this.collectUnionBranches(target);
|
|
964
|
+
if ("code" in branches) {
|
|
965
|
+
return 0;
|
|
966
|
+
}
|
|
967
|
+
return this.collectSelectOutputs(root, branches[0]).filter(item => normalizeIdentifier(item.name) === normalizeIdentifier(columnName)).length;
|
|
968
|
+
}
|
|
969
|
+
if (!(target instanceof SimpleSelectQuery)) {
|
|
970
|
+
return 0;
|
|
971
|
+
}
|
|
972
|
+
return this.collectSelectOutputs(root, target).filter(item => normalizeIdentifier(item.name) === normalizeIdentifier(columnName)).length;
|
|
973
|
+
}
|
|
974
|
+
collectUnionBranches(query) {
|
|
975
|
+
const branches = [];
|
|
976
|
+
const visit = (select) => {
|
|
977
|
+
var _a;
|
|
978
|
+
if (select instanceof SimpleSelectQuery) {
|
|
979
|
+
branches.push(select);
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
if (!(select instanceof BinarySelectQuery)) {
|
|
983
|
+
return {
|
|
984
|
+
code: "UNION_BOUNDARY",
|
|
985
|
+
reason: "Predicate would need distribution into a non-simple UNION branch, which is unsupported."
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
const operator = select.operator.value.trim().toLowerCase();
|
|
989
|
+
if (operator !== "union" && operator !== "union all") {
|
|
990
|
+
return {
|
|
991
|
+
code: "UNION_BOUNDARY",
|
|
992
|
+
reason: `Predicate would need distribution through '${select.operator.value}', which is unsupported.`
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
return (_a = visit(select.left)) !== null && _a !== void 0 ? _a : visit(select.right);
|
|
996
|
+
};
|
|
997
|
+
const skip = visit(query);
|
|
998
|
+
return skip !== null && skip !== void 0 ? skip : branches;
|
|
999
|
+
}
|
|
1000
|
+
resolveUnionOutputIndex(root, firstBranch, outputColumnName) {
|
|
1001
|
+
const matches = [];
|
|
1002
|
+
this.collectSelectOutputs(root, firstBranch).forEach((item, index) => {
|
|
1003
|
+
if (normalizeIdentifier(item.name) === normalizeIdentifier(outputColumnName)) {
|
|
1004
|
+
matches.push(index);
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
if (matches.length !== 1) {
|
|
1008
|
+
return {
|
|
1009
|
+
code: "AMBIGUOUS_TARGET_COLUMN",
|
|
1010
|
+
reason: matches.length === 0
|
|
1011
|
+
? `UNION output does not expose '${outputColumnName}' from its first branch.`
|
|
1012
|
+
: `UNION first branch exposes multiple '${outputColumnName}' columns.`
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
return { index: matches[0] };
|
|
1016
|
+
}
|
|
1017
|
+
resolveTargetColumnByOutputIndex(root, query, outputIndex, sourceColumn) {
|
|
1018
|
+
const output = this.collectSelectOutputs(root, query)[outputIndex];
|
|
1019
|
+
if (!output) {
|
|
1020
|
+
return {
|
|
1021
|
+
code: "UNION_COLUMN_MISMATCH",
|
|
1022
|
+
reason: `UNION branch does not expose column '${sourceColumn.column.name}' at output position ${outputIndex + 1}.`
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
if (!(output.value instanceof ColumnReference)) {
|
|
1026
|
+
return {
|
|
1027
|
+
code: "EXPRESSION_OUTPUT_UNSUPPORTED",
|
|
1028
|
+
reason: `UNION branch output at position ${outputIndex + 1} for '${sourceColumn.column.name}' is an expression, not a direct column reference.`
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
const sourceResolution = this.verifyColumnResolvableInQuery(query, output.value);
|
|
1032
|
+
if (sourceResolution) {
|
|
1033
|
+
return sourceResolution;
|
|
1034
|
+
}
|
|
1035
|
+
return {
|
|
1036
|
+
sourceColumn,
|
|
1037
|
+
targetColumn: output.value
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
collectSelectOutputs(root, query) {
|
|
1041
|
+
var _a, _b, _c, _d;
|
|
1042
|
+
const commonTables = [
|
|
1043
|
+
...((_b = (_a = query.withClause) === null || _a === void 0 ? void 0 : _a.tables) !== null && _b !== void 0 ? _b : []),
|
|
1044
|
+
...((_d = (_c = root.withClause) === null || _c === void 0 ? void 0 : _c.tables) !== null && _d !== void 0 ? _d : [])
|
|
1045
|
+
];
|
|
1046
|
+
const collector = new SelectOutputCollector(null, commonTables.length > 0 ? commonTables : null);
|
|
1047
|
+
return collector.collect(query);
|
|
1048
|
+
}
|
|
1049
|
+
resolveSourceQueryForColumns(root, source) {
|
|
1050
|
+
var _a;
|
|
1051
|
+
if (source.datasource instanceof SubQuerySource) {
|
|
1052
|
+
return source.datasource.query;
|
|
1053
|
+
}
|
|
1054
|
+
if (source.datasource instanceof TableSource) {
|
|
1055
|
+
const cteQuery = (_a = this.findCte(root, source.datasource.table.name)) === null || _a === void 0 ? void 0 : _a.query;
|
|
1056
|
+
return cteQuery instanceof SimpleSelectQuery || cteQuery instanceof BinarySelectQuery
|
|
1057
|
+
? cteQuery
|
|
1058
|
+
: null;
|
|
1059
|
+
}
|
|
1060
|
+
return null;
|
|
1061
|
+
}
|
|
1062
|
+
findCte(root, name) {
|
|
1063
|
+
var _a, _b;
|
|
1064
|
+
const normalized = normalizeIdentifier(name);
|
|
1065
|
+
const matches = ((_b = (_a = root.withClause) === null || _a === void 0 ? void 0 : _a.tables) !== null && _b !== void 0 ? _b : [])
|
|
1066
|
+
.filter(table => normalizeIdentifier(table.getSourceAliasName()) === normalized);
|
|
1067
|
+
return matches.length === 1 ? matches[0] : null;
|
|
1068
|
+
}
|
|
1069
|
+
countTableSourceReferences(query, tableName) {
|
|
1070
|
+
const normalized = normalizeIdentifier(tableName);
|
|
1071
|
+
let count = 0;
|
|
1072
|
+
const visitSelect = (select) => {
|
|
1073
|
+
var _a, _b;
|
|
1074
|
+
if (select instanceof BinarySelectQuery) {
|
|
1075
|
+
visitSelect(select.left);
|
|
1076
|
+
visitSelect(select.right);
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
if (!(select instanceof SimpleSelectQuery)) {
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
if (select.fromClause) {
|
|
1083
|
+
for (const binding of this.getSourceBindings(select.fromClause)) {
|
|
1084
|
+
const source = binding.source.datasource;
|
|
1085
|
+
if (source instanceof TableSource && normalizeIdentifier(source.table.name) === normalized) {
|
|
1086
|
+
count += 1;
|
|
1087
|
+
}
|
|
1088
|
+
if (source instanceof SubQuerySource) {
|
|
1089
|
+
visitSelect(source.query);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
for (const cte of (_b = (_a = select.withClause) === null || _a === void 0 ? void 0 : _a.tables) !== null && _b !== void 0 ? _b : []) {
|
|
1094
|
+
if (cte.query instanceof SimpleSelectQuery || cte.query instanceof BinarySelectQuery) {
|
|
1095
|
+
visitSelect(cte.query);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
visitSelect(query);
|
|
1100
|
+
return count;
|
|
1101
|
+
}
|
|
1102
|
+
collectSourceAliases(query) {
|
|
1103
|
+
var _a, _b;
|
|
1104
|
+
const aliases = new Set();
|
|
1105
|
+
for (const source of (_b = (_a = query.fromClause) === null || _a === void 0 ? void 0 : _a.getSources()) !== null && _b !== void 0 ? _b : []) {
|
|
1106
|
+
const alias = source.getAliasName();
|
|
1107
|
+
if (alias) {
|
|
1108
|
+
aliases.add(normalizeIdentifier(alias));
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return aliases;
|
|
1112
|
+
}
|
|
1113
|
+
collectRootColumnReferences(root, expression) {
|
|
1114
|
+
const references = [];
|
|
1115
|
+
const rootAliases = this.collectSourceAliases(root);
|
|
1116
|
+
const visitSelect = (query, inheritedLocalAliases) => {
|
|
1117
|
+
var _a, _b, _c, _d;
|
|
1118
|
+
if (!(query instanceof SimpleSelectQuery)) {
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const localAliases = new Set(inheritedLocalAliases);
|
|
1122
|
+
for (const alias of this.collectSourceAliases(query)) {
|
|
1123
|
+
localAliases.add(alias);
|
|
1124
|
+
}
|
|
1125
|
+
query.selectClause.items.forEach(item => visit(item.value, localAliases));
|
|
1126
|
+
for (const join of (_b = (_a = query.fromClause) === null || _a === void 0 ? void 0 : _a.joins) !== null && _b !== void 0 ? _b : []) {
|
|
1127
|
+
if (join.condition) {
|
|
1128
|
+
visit(join.condition.condition, localAliases);
|
|
1129
|
+
}
|
|
1130
|
+
const source = join.source.datasource;
|
|
1131
|
+
if (source instanceof SubQuerySource) {
|
|
1132
|
+
visitSelect(source.query, localAliases);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (query.whereClause) {
|
|
1136
|
+
visit(query.whereClause.condition, localAliases);
|
|
1137
|
+
}
|
|
1138
|
+
if (query.havingClause) {
|
|
1139
|
+
visit(query.havingClause.condition, localAliases);
|
|
1140
|
+
}
|
|
1141
|
+
for (const cte of (_d = (_c = query.withClause) === null || _c === void 0 ? void 0 : _c.tables) !== null && _d !== void 0 ? _d : []) {
|
|
1142
|
+
if (cte.query instanceof SimpleSelectQuery || cte.query instanceof BinarySelectQuery) {
|
|
1143
|
+
visitSelect(cte.query, localAliases);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
const collect = (reference) => {
|
|
1148
|
+
if (!references.some(existing => sameColumnReference(existing, reference))) {
|
|
1149
|
+
references.push(reference);
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
const visit = (value, localAliases) => {
|
|
1153
|
+
const candidate = unwrapParens(value);
|
|
1154
|
+
if (candidate instanceof ColumnReference) {
|
|
1155
|
+
const namespace = normalizeIdentifier(candidate.getNamespace());
|
|
1156
|
+
if (namespace) {
|
|
1157
|
+
if (rootAliases.has(namespace) && !localAliases.has(namespace)) {
|
|
1158
|
+
collect(candidate);
|
|
1159
|
+
}
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (localAliases.size === 0) {
|
|
1163
|
+
collect(candidate);
|
|
1164
|
+
}
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
if (candidate instanceof BinaryExpression) {
|
|
1168
|
+
visit(candidate.left, localAliases);
|
|
1169
|
+
visit(candidate.right, localAliases);
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
if (candidate instanceof UnaryExpression) {
|
|
1173
|
+
visit(candidate.expression, localAliases);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (candidate instanceof InlineQuery) {
|
|
1177
|
+
visitSelect(candidate.selectQuery, localAliases);
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
if (candidate instanceof FunctionCall) {
|
|
1181
|
+
if (candidate.argument) {
|
|
1182
|
+
visit(candidate.argument, localAliases);
|
|
1183
|
+
}
|
|
1184
|
+
if (candidate.filterCondition) {
|
|
1185
|
+
visit(candidate.filterCondition, localAliases);
|
|
1186
|
+
}
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
if (candidate instanceof CastExpression) {
|
|
1190
|
+
visit(candidate.input, localAliases);
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
if (candidate instanceof CaseExpression) {
|
|
1194
|
+
if (candidate.condition) {
|
|
1195
|
+
visit(candidate.condition, localAliases);
|
|
1196
|
+
}
|
|
1197
|
+
for (const pair of candidate.switchCase.cases) {
|
|
1198
|
+
visit(pair.key, localAliases);
|
|
1199
|
+
visit(pair.value, localAliases);
|
|
1200
|
+
}
|
|
1201
|
+
if (candidate.switchCase.elseValue) {
|
|
1202
|
+
visit(candidate.switchCase.elseValue, localAliases);
|
|
1203
|
+
}
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
if (candidate instanceof BetweenExpression) {
|
|
1207
|
+
visit(candidate.expression, localAliases);
|
|
1208
|
+
visit(candidate.lower, localAliases);
|
|
1209
|
+
visit(candidate.upper, localAliases);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
if (candidate instanceof JsonPredicateExpression) {
|
|
1213
|
+
visit(candidate.expression, localAliases);
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
if (candidate instanceof ArrayExpression) {
|
|
1217
|
+
visit(candidate.expression, localAliases);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (candidate instanceof ArrayQueryExpression) {
|
|
1221
|
+
visitSelect(candidate.query, localAliases);
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
if (candidate instanceof ValueList) {
|
|
1225
|
+
candidate.values.forEach(item => visit(item, localAliases));
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
if (candidate instanceof TupleExpression) {
|
|
1229
|
+
candidate.values.forEach(item => visit(item, localAliases));
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (candidate instanceof TypeValue && candidate.argument) {
|
|
1233
|
+
visit(candidate.argument, localAliases);
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
visit(expression, new Set());
|
|
1237
|
+
return references;
|
|
1238
|
+
}
|
|
1239
|
+
hasWindowUsage(query) {
|
|
1240
|
+
if (query.windowClause) {
|
|
1241
|
+
return true;
|
|
1242
|
+
}
|
|
1243
|
+
let found = false;
|
|
1244
|
+
const visit = (value) => {
|
|
1245
|
+
if (found) {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const candidate = unwrapParens(value);
|
|
1249
|
+
if (candidate instanceof FunctionCall) {
|
|
1250
|
+
if (candidate.over) {
|
|
1251
|
+
found = true;
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
if (candidate.argument) {
|
|
1255
|
+
visit(candidate.argument);
|
|
1256
|
+
}
|
|
1257
|
+
if (candidate.filterCondition) {
|
|
1258
|
+
visit(candidate.filterCondition);
|
|
1259
|
+
}
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
if (candidate instanceof BinaryExpression) {
|
|
1263
|
+
visit(candidate.left);
|
|
1264
|
+
visit(candidate.right);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
if (candidate instanceof UnaryExpression) {
|
|
1268
|
+
visit(candidate.expression);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
if (candidate instanceof CastExpression) {
|
|
1272
|
+
visit(candidate.input);
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
if (candidate instanceof CaseExpression) {
|
|
1276
|
+
if (candidate.condition) {
|
|
1277
|
+
visit(candidate.condition);
|
|
1278
|
+
}
|
|
1279
|
+
for (const pair of candidate.switchCase.cases) {
|
|
1280
|
+
visit(pair.key);
|
|
1281
|
+
visit(pair.value);
|
|
1282
|
+
}
|
|
1283
|
+
if (candidate.switchCase.elseValue) {
|
|
1284
|
+
visit(candidate.switchCase.elseValue);
|
|
1285
|
+
}
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
if (candidate instanceof ValueList) {
|
|
1289
|
+
candidate.values.forEach(visit);
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
query.selectClause.items.forEach(item => visit(item.value));
|
|
1293
|
+
return found;
|
|
1294
|
+
}
|
|
1295
|
+
collectParameterNames(expression) {
|
|
1296
|
+
const names = [];
|
|
1297
|
+
const visitSelect = (query) => {
|
|
1298
|
+
var _a, _b, _c, _d;
|
|
1299
|
+
if (query instanceof BinarySelectQuery) {
|
|
1300
|
+
visitSelect(query.left);
|
|
1301
|
+
visitSelect(query.right);
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
if (!(query instanceof SimpleSelectQuery)) {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
query.selectClause.items.forEach(item => visit(item.value));
|
|
1308
|
+
for (const join of (_b = (_a = query.fromClause) === null || _a === void 0 ? void 0 : _a.joins) !== null && _b !== void 0 ? _b : []) {
|
|
1309
|
+
if (join.condition) {
|
|
1310
|
+
visit(join.condition.condition);
|
|
1311
|
+
}
|
|
1312
|
+
const source = join.source.datasource;
|
|
1313
|
+
if (source instanceof SubQuerySource) {
|
|
1314
|
+
visitSelect(source.query);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
if (query.whereClause) {
|
|
1318
|
+
visit(query.whereClause.condition);
|
|
1319
|
+
}
|
|
1320
|
+
if (query.havingClause) {
|
|
1321
|
+
visit(query.havingClause.condition);
|
|
1322
|
+
}
|
|
1323
|
+
for (const cte of (_d = (_c = query.withClause) === null || _c === void 0 ? void 0 : _c.tables) !== null && _d !== void 0 ? _d : []) {
|
|
1324
|
+
if (cte.query instanceof SimpleSelectQuery || cte.query instanceof BinarySelectQuery) {
|
|
1325
|
+
visitSelect(cte.query);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
const visit = (value) => {
|
|
1330
|
+
const candidate = unwrapParens(value);
|
|
1331
|
+
if (candidate instanceof ParameterExpression) {
|
|
1332
|
+
appendUnique(names, candidate.name.value);
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
if (candidate instanceof BinaryExpression) {
|
|
1336
|
+
visit(candidate.left);
|
|
1337
|
+
visit(candidate.right);
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
if (candidate instanceof UnaryExpression) {
|
|
1341
|
+
visit(candidate.expression);
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
if (candidate instanceof InlineQuery) {
|
|
1345
|
+
visitSelect(candidate.selectQuery);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
if (candidate instanceof ArrayQueryExpression) {
|
|
1349
|
+
visitSelect(candidate.query);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
if (candidate instanceof FunctionCall) {
|
|
1353
|
+
if (candidate.argument) {
|
|
1354
|
+
visit(candidate.argument);
|
|
1355
|
+
}
|
|
1356
|
+
if (candidate.filterCondition) {
|
|
1357
|
+
visit(candidate.filterCondition);
|
|
1358
|
+
}
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
if (candidate instanceof CastExpression) {
|
|
1362
|
+
visit(candidate.input);
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
if (candidate instanceof CaseExpression) {
|
|
1366
|
+
if (candidate.condition) {
|
|
1367
|
+
visit(candidate.condition);
|
|
1368
|
+
}
|
|
1369
|
+
for (const pair of candidate.switchCase.cases) {
|
|
1370
|
+
visit(pair.key);
|
|
1371
|
+
visit(pair.value);
|
|
1372
|
+
}
|
|
1373
|
+
if (candidate.switchCase.elseValue) {
|
|
1374
|
+
visit(candidate.switchCase.elseValue);
|
|
1375
|
+
}
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
if (candidate instanceof BetweenExpression) {
|
|
1379
|
+
visit(candidate.expression);
|
|
1380
|
+
visit(candidate.lower);
|
|
1381
|
+
visit(candidate.upper);
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
if (candidate instanceof JsonPredicateExpression) {
|
|
1385
|
+
visit(candidate.expression);
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
if (candidate instanceof ArrayExpression) {
|
|
1389
|
+
visit(candidate.expression);
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
if (candidate instanceof ValueList) {
|
|
1393
|
+
candidate.values.forEach(visit);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
if (candidate instanceof TupleExpression) {
|
|
1397
|
+
candidate.values.forEach(visit);
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
if (candidate instanceof TypeValue && candidate.argument) {
|
|
1401
|
+
visit(candidate.argument);
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
visit(expression);
|
|
1405
|
+
return names;
|
|
1406
|
+
}
|
|
1407
|
+
makeSkipped(expression, draft, options) {
|
|
1408
|
+
return Object.assign({ predicateSql: formatSqlComponent(expression, options), scopeId: "scope:root" }, draft);
|
|
1409
|
+
}
|
|
1410
|
+
buildResult(params) {
|
|
1411
|
+
return {
|
|
1412
|
+
ok: params.errors.length === 0,
|
|
1413
|
+
sql: params.sql,
|
|
1414
|
+
query: params.query,
|
|
1415
|
+
applied: params.applied,
|
|
1416
|
+
skipped: params.skipped,
|
|
1417
|
+
warnings: params.warnings,
|
|
1418
|
+
errors: params.errors,
|
|
1419
|
+
safety: {
|
|
1420
|
+
mode: "safe_only",
|
|
1421
|
+
unsafeRewriteApplied: false,
|
|
1422
|
+
dryRun: params.dryRun,
|
|
1423
|
+
formatterGeneratedSource: params.formatterGeneratedSource
|
|
1424
|
+
},
|
|
1425
|
+
staticPredicateMoves: params.applied
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
export const planStaticPredicatePlacement = (input, options = {}) => {
|
|
1430
|
+
return new StaticPredicatePlacementOptimizer().plan(input, options);
|
|
1431
|
+
};
|
|
1432
|
+
export const optimizeStaticPredicatePlacement = (input, options = {}) => {
|
|
1433
|
+
return new StaticPredicatePlacementOptimizer().optimize(input, options);
|
|
1434
|
+
};
|
|
1435
|
+
//# sourceMappingURL=StaticPredicatePlacementOptimizer.js.map
|