rawsql-ts 0.28.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/esm/index.d.ts +1 -0
  2. package/dist/esm/index.js +1 -0
  3. package/dist/esm/index.js.map +1 -1
  4. package/dist/esm/index.min.js +2 -2
  5. package/dist/esm/index.min.js.map +4 -4
  6. package/dist/esm/transformers/ConditionDeduplicationOptimizer.d.ts +22 -0
  7. package/dist/esm/transformers/ConditionDeduplicationOptimizer.js +198 -0
  8. package/dist/esm/transformers/ConditionDeduplicationOptimizer.js.map +1 -0
  9. package/dist/esm/transformers/ConditionOptimization.d.ts +31 -1
  10. package/dist/esm/transformers/ConditionOptimization.js +321 -4
  11. package/dist/esm/transformers/ConditionOptimization.js.map +1 -1
  12. package/dist/esm/transformers/ParameterConditionPlacementOptimizer.d.ts +13 -0
  13. package/dist/esm/transformers/ParameterConditionPlacementOptimizer.js +238 -87
  14. package/dist/esm/transformers/ParameterConditionPlacementOptimizer.js.map +1 -1
  15. package/dist/esm/transformers/PredicateExpressionUtils.d.ts +16 -0
  16. package/dist/esm/transformers/PredicateExpressionUtils.js +74 -0
  17. package/dist/esm/transformers/PredicateExpressionUtils.js.map +1 -0
  18. package/dist/esm/transformers/PredicateReachabilityAnalyzer.d.ts +88 -0
  19. package/dist/esm/transformers/PredicateReachabilityAnalyzer.js +495 -0
  20. package/dist/esm/transformers/PredicateReachabilityAnalyzer.js.map +1 -0
  21. package/dist/esm/transformers/StaticPredicatePlacementOptimizer.d.ts +14 -0
  22. package/dist/esm/transformers/StaticPredicatePlacementOptimizer.js +344 -157
  23. package/dist/esm/transformers/StaticPredicatePlacementOptimizer.js.map +1 -1
  24. package/dist/esm/transformers/TopLevelAndConditionDeduper.d.ts +10 -0
  25. package/dist/esm/transformers/TopLevelAndConditionDeduper.js +66 -0
  26. package/dist/esm/transformers/TopLevelAndConditionDeduper.js.map +1 -0
  27. package/dist/index.js +1 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.min.js +2 -2
  30. package/dist/index.min.js.map +4 -4
  31. package/dist/src/index.d.ts +1 -0
  32. package/dist/src/transformers/ConditionDeduplicationOptimizer.d.ts +22 -0
  33. package/dist/src/transformers/ConditionOptimization.d.ts +31 -1
  34. package/dist/src/transformers/ParameterConditionPlacementOptimizer.d.ts +13 -0
  35. package/dist/src/transformers/PredicateExpressionUtils.d.ts +16 -0
  36. package/dist/src/transformers/PredicateReachabilityAnalyzer.d.ts +88 -0
  37. package/dist/src/transformers/StaticPredicatePlacementOptimizer.d.ts +14 -0
  38. package/dist/src/transformers/TopLevelAndConditionDeduper.d.ts +10 -0
  39. package/dist/transformers/ConditionDeduplicationOptimizer.js +202 -0
  40. package/dist/transformers/ConditionDeduplicationOptimizer.js.map +1 -0
  41. package/dist/transformers/ConditionOptimization.js +324 -4
  42. package/dist/transformers/ConditionOptimization.js.map +1 -1
  43. package/dist/transformers/ParameterConditionPlacementOptimizer.js +261 -110
  44. package/dist/transformers/ParameterConditionPlacementOptimizer.js.map +1 -1
  45. package/dist/transformers/PredicateExpressionUtils.js +88 -0
  46. package/dist/transformers/PredicateExpressionUtils.js.map +1 -0
  47. package/dist/transformers/PredicateReachabilityAnalyzer.js +508 -0
  48. package/dist/transformers/PredicateReachabilityAnalyzer.js.map +1 -0
  49. package/dist/transformers/StaticPredicatePlacementOptimizer.js +366 -179
  50. package/dist/transformers/StaticPredicatePlacementOptimizer.js.map +1 -1
  51. package/dist/transformers/TopLevelAndConditionDeduper.js +72 -0
  52. package/dist/transformers/TopLevelAndConditionDeduper.js.map +1 -0
  53. package/dist/tsconfig.browser.tsbuildinfo +1 -1
  54. package/package.json +1 -1
@@ -5,70 +5,11 @@ const Clause_1 = require("../models/Clause");
5
5
  const SelectQuery_1 = require("../models/SelectQuery");
6
6
  const ValueComponent_1 = require("../models/ValueComponent");
7
7
  const SelectQueryParser_1 = require("../parsers/SelectQueryParser");
8
- const ValueParser_1 = require("../parsers/ValueParser");
9
8
  const SqlComponentFormatter_1 = require("./SqlComponentFormatter");
10
9
  const SelectOutputCollector_1 = require("./SelectOutputCollector");
10
+ const TopLevelAndConditionDeduper_1 = require("./TopLevelAndConditionDeduper");
11
+ const PredicateExpressionUtils_1 = require("./PredicateExpressionUtils");
11
12
  const VOLATILE_OR_UNSUPPORTED_FUNCTION_REASON = "Predicate contains a function call; volatile and expression predicates are not moved in the safe-only implementation.";
12
- const normalizeIdentifier = (value) => value.trim().toLowerCase();
13
- const unwrapParens = (expression) => {
14
- let candidate = expression;
15
- while (candidate instanceof ValueComponent_1.ParenExpression) {
16
- candidate = candidate.expression;
17
- }
18
- return candidate;
19
- };
20
- const isBinaryOperator = (expression, operator) => {
21
- const candidate = unwrapParens(expression);
22
- return candidate instanceof ValueComponent_1.BinaryExpression
23
- && candidate.operator.value.trim().toLowerCase() === operator;
24
- };
25
- const collectTopLevelAndTerms = (expression) => {
26
- const candidate = unwrapParens(expression);
27
- if (!isBinaryOperator(candidate, "and")) {
28
- return [expression];
29
- }
30
- return [
31
- ...collectTopLevelAndTerms(candidate.left),
32
- ...collectTopLevelAndTerms(candidate.right)
33
- ];
34
- };
35
- const rebuildWhereWithoutTerms = (query, termsToRemove) => {
36
- if (!query.whereClause || termsToRemove.size === 0) {
37
- return;
38
- }
39
- const remaining = collectTopLevelAndTerms(query.whereClause.condition)
40
- .filter(term => !termsToRemove.has(term));
41
- if (remaining.length === 0) {
42
- query.whereClause = null;
43
- return;
44
- }
45
- let rebuilt = remaining[0];
46
- for (let index = 1; index < remaining.length; index += 1) {
47
- rebuilt = new ValueComponent_1.BinaryExpression(rebuilt, "and", remaining[index]);
48
- }
49
- query.whereClause = new Clause_1.WhereClause(rebuilt);
50
- };
51
- const cloneValueComponent = (expression, options) => {
52
- return ValueParser_1.ValueParser.parse((0, SqlComponentFormatter_1.formatSqlComponent)(expression, options));
53
- };
54
- const cloneColumnReference = (reference) => {
55
- var _a, _b;
56
- const namespaces = (_b = (_a = reference.namespaces) === null || _a === void 0 ? void 0 : _a.map(namespace => namespace.name)) !== null && _b !== void 0 ? _b : null;
57
- return new ValueComponent_1.ColumnReference(namespaces, reference.column.name);
58
- };
59
- const columnReferenceText = (reference) => {
60
- const namespace = reference.getNamespace();
61
- return namespace ? `${namespace}.${reference.column.name}` : reference.column.name;
62
- };
63
- const sameColumnReference = (left, right) => {
64
- return normalizeIdentifier(left.column.name) === normalizeIdentifier(right.column.name)
65
- && normalizeIdentifier(left.getNamespace()) === normalizeIdentifier(right.getNamespace());
66
- };
67
- const appendUnique = (items, value) => {
68
- if (!items.includes(value)) {
69
- items.push(value);
70
- }
71
- };
72
13
  class StaticPredicatePlacementOptimizer {
73
14
  plan(input, options = {}) {
74
15
  var _a, _b, _c;
@@ -106,71 +47,8 @@ class StaticPredicatePlacementOptimizer {
106
47
  const query = parsed.query;
107
48
  const applied = [];
108
49
  const skipped = [];
109
- const movedTerms = [];
110
- for (const term of query.whereClause ? collectTopLevelAndTerms(query.whereClause.condition) : []) {
111
- const candidate = this.analyzeCandidate(query, term, options);
112
- if (!candidate) {
113
- continue;
114
- }
115
- if ("code" in candidate) {
116
- skipped.push(this.makeSkipped(term, candidate, options));
117
- continue;
118
- }
119
- const target = this.resolveTarget(query, candidate);
120
- if ("code" in target) {
121
- skipped.push(this.makeSkipped(term, target, options));
122
- continue;
123
- }
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.";
162
- }
163
- movedTerms.push(term);
164
- applied.push({
165
- kind: "move_static_predicate",
166
- predicateSql: candidate.predicateSql,
167
- fromScopeId: "scope:root",
168
- toScopeId: target.scopeId,
169
- reason: appliedReason,
170
- columnReferences: candidate.references.map(columnReferenceText)
171
- });
172
- }
173
- rebuildWhereWithoutTerms(query, new Set(movedTerms));
50
+ this.placePredicatesInScopes(query, options, applied, skipped);
51
+ // API output shape review: keep result.sql/result.query compatibility while expanding scope-aware placement.
174
52
  const sql = applied.length > 0 || (0, SqlComponentFormatter_1.hasSqlComponentFormatOverride)(options)
175
53
  ? (0, SqlComponentFormatter_1.formatSqlComponent)(query, options)
176
54
  : parsed.sql;
@@ -185,6 +63,121 @@ class StaticPredicatePlacementOptimizer {
185
63
  formatterGeneratedSource: parsed.formatterGeneratedSource
186
64
  });
187
65
  }
66
+ placePredicatesInScopes(contextRoot, options, applied, skipped) {
67
+ var _a;
68
+ const queue = [{ query: contextRoot, scopeId: "scope:root" }];
69
+ let processed = 0;
70
+ while (queue.length > 0) {
71
+ processed += 1;
72
+ if (processed > 1000) {
73
+ skipped.push({
74
+ predicateSql: "",
75
+ scopeId: "scope:root",
76
+ code: "RECURSIVE_PLACEMENT_LIMIT",
77
+ reason: "Static predicate recursive placement stopped after reaching the safety iteration limit."
78
+ });
79
+ return;
80
+ }
81
+ const work = queue.shift();
82
+ const movedTerms = [];
83
+ const reportSkipped = work.pendingTerms === undefined;
84
+ const terms = (_a = work.pendingTerms) !== null && _a !== void 0 ? _a : (work.query.whereClause
85
+ ? (0, PredicateExpressionUtils_1.collectTopLevelAndTerms)(work.query.whereClause.condition)
86
+ : []);
87
+ for (const term of terms) {
88
+ const candidate = this.analyzeCandidate(work.query, term, options);
89
+ if (!candidate) {
90
+ continue;
91
+ }
92
+ if ("code" in candidate) {
93
+ if (reportSkipped) {
94
+ skipped.push(this.makeSkipped(term, work.scopeId, candidate, options));
95
+ }
96
+ continue;
97
+ }
98
+ const target = this.resolveTarget(contextRoot, work.query, candidate);
99
+ if ("code" in target) {
100
+ if (reportSkipped) {
101
+ skipped.push(this.makeSkipped(term, work.scopeId, target, options));
102
+ }
103
+ continue;
104
+ }
105
+ let appliedReason = "";
106
+ if (target.kind === "join_on") {
107
+ this.appendJoinOnPredicate(target.join, candidate.expression, options);
108
+ appliedReason = target.reason;
109
+ }
110
+ else if (target.kind === "simple") {
111
+ const targetColumns = this.resolveTargetColumns(contextRoot, target.query, candidate.references);
112
+ if ("code" in targetColumns) {
113
+ if (reportSkipped) {
114
+ skipped.push(this.makeSkipped(term, work.scopeId, targetColumns, options));
115
+ }
116
+ continue;
117
+ }
118
+ const placement = this.resolveTargetPlacement(contextRoot, target.query, targetColumns);
119
+ if ("code" in placement) {
120
+ if (reportSkipped) {
121
+ skipped.push(this.makeSkipped(term, work.scopeId, placement, options));
122
+ }
123
+ continue;
124
+ }
125
+ const movedPredicate = this.rebasePredicate(candidate.expression, targetColumns, options);
126
+ target.query.appendWhere(movedPredicate);
127
+ (0, TopLevelAndConditionDeduper_1.dedupeWhereTopLevelAndConditions)(target.query, options);
128
+ appliedReason = work.scopeId === "scope:root"
129
+ ? placement.reason
130
+ : `${placement.reason} Predicate was safely rebased from a previously moved predicate.`;
131
+ const retainedTerms = target.query.whereClause
132
+ ? (0, PredicateExpressionUtils_1.collectTopLevelAndTerms)(target.query.whereClause.condition)
133
+ : [];
134
+ if (retainedTerms.includes(movedPredicate)) {
135
+ queue.push({
136
+ query: target.query,
137
+ scopeId: target.scopeId,
138
+ pendingTerms: [movedPredicate]
139
+ });
140
+ }
141
+ }
142
+ else {
143
+ const placements = [];
144
+ let skip = null;
145
+ for (const branch of target.branches) {
146
+ const placement = this.resolveTargetPlacement(contextRoot, branch.query, branch.targetColumns);
147
+ if ("code" in placement) {
148
+ skip = placement;
149
+ break;
150
+ }
151
+ placements.push(placement);
152
+ }
153
+ if (skip) {
154
+ if (reportSkipped) {
155
+ skipped.push(this.makeSkipped(term, work.scopeId, skip, options));
156
+ }
157
+ continue;
158
+ }
159
+ for (const branch of target.branches) {
160
+ const movedPredicate = this.rebasePredicate(candidate.expression, branch.targetColumns, options);
161
+ branch.query.appendWhere(movedPredicate);
162
+ (0, TopLevelAndConditionDeduper_1.dedupeWhereTopLevelAndConditions)(branch.query, options);
163
+ }
164
+ appliedReason = placements.some(item => /group by/i.test(item.reason))
165
+ ? "Predicate is distributed to every UNION branch by output column position; grouped branches only receive GROUP BY-key predicates."
166
+ : "Predicate is distributed to every UNION branch by output column position before unsafe query boundaries.";
167
+ }
168
+ movedTerms.push(term);
169
+ applied.push({
170
+ kind: "move_static_predicate",
171
+ predicateSql: candidate.predicateSql,
172
+ fromScopeId: work.scopeId,
173
+ toScopeId: target.scopeId,
174
+ reason: appliedReason,
175
+ columnReferences: candidate.references.map(PredicateExpressionUtils_1.columnReferenceText)
176
+ });
177
+ }
178
+ (0, PredicateExpressionUtils_1.rebuildWhereWithoutTerms)(work.query, new Set(movedTerms));
179
+ }
180
+ }
188
181
  optimize(input, options = {}) {
189
182
  var _a;
190
183
  return this.plan(input, { ...options, dryRun: (_a = options.dryRun) !== null && _a !== void 0 ? _a : false });
@@ -257,12 +250,12 @@ class StaticPredicatePlacementOptimizer {
257
250
  };
258
251
  }
259
252
  isExistsPredicate(expression) {
260
- const candidate = unwrapParens(expression);
253
+ const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(expression);
261
254
  if (!(candidate instanceof ValueComponent_1.UnaryExpression)) {
262
255
  return false;
263
256
  }
264
257
  const operator = candidate.operator.value.trim().toLowerCase();
265
- return operator === "exists" && unwrapParens(candidate.expression) instanceof ValueComponent_1.InlineQuery;
258
+ return operator === "exists" && (0, PredicateExpressionUtils_1.unwrapParens)(candidate.expression) instanceof ValueComponent_1.InlineQuery;
266
259
  }
267
260
  findUnsupportedExpression(expression, allowExistsSubquery) {
268
261
  let found = null;
@@ -311,7 +304,7 @@ class StaticPredicatePlacementOptimizer {
311
304
  if (found) {
312
305
  return;
313
306
  }
314
- const candidate = unwrapParens(value);
307
+ const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(value);
315
308
  if (candidate instanceof ValueComponent_1.BinaryExpression) {
316
309
  visit(candidate.left);
317
310
  visit(candidate.right);
@@ -380,12 +373,13 @@ class StaticPredicatePlacementOptimizer {
380
373
  visit(expression);
381
374
  return found;
382
375
  }
383
- resolveTarget(root, candidate) {
384
- const boundary = this.findRootQueryBoundary(root);
376
+ resolveTarget(contextRoot, currentQuery, candidate) {
377
+ var _a;
378
+ const boundary = this.findRootQueryBoundary(currentQuery);
385
379
  if (boundary) {
386
380
  return boundary;
387
381
  }
388
- if (!root.fromClause) {
382
+ if (!currentQuery.fromClause) {
389
383
  return {
390
384
  code: "NO_FROM_CLAUSE",
391
385
  reason: "Predicate has no FROM source that can receive it safely."
@@ -393,7 +387,7 @@ class StaticPredicatePlacementOptimizer {
393
387
  }
394
388
  const bindings = [];
395
389
  for (const reference of candidate.references) {
396
- const binding = this.resolveSourceBinding(root, root, reference);
390
+ const binding = this.resolveSourceBinding(contextRoot, currentQuery, reference);
397
391
  if ("code" in binding) {
398
392
  return binding;
399
393
  }
@@ -408,12 +402,25 @@ class StaticPredicatePlacementOptimizer {
408
402
  };
409
403
  }
410
404
  const binding = bindings[0];
411
- const nullableSide = this.findNullableSideBoundary(root.fromClause, binding);
405
+ if ((_a = binding.join) === null || _a === void 0 ? void 0 : _a.lateral) {
406
+ return {
407
+ code: "LATERAL_JOIN_BOUNDARY",
408
+ reason: "Predicate crosses a LATERAL JOIN boundary; moving it may change semantics."
409
+ };
410
+ }
411
+ const nullableSide = this.findNullableSideBoundary(currentQuery.fromClause, binding);
412
412
  if (nullableSide) {
413
413
  return nullableSide;
414
414
  }
415
- const upstream = this.resolveUpstreamQuery(root, binding, candidate.references);
415
+ const upstream = this.resolveUpstreamQuery(contextRoot, binding, candidate.references);
416
416
  if ("code" in upstream) {
417
+ if (upstream.code === "NO_SAFE_UPSTREAM_QUERY") {
418
+ const joinOnTarget = this.resolveBaseTableJoinOnTarget(contextRoot, currentQuery, binding);
419
+ if (!("code" in joinOnTarget)) {
420
+ return joinOnTarget;
421
+ }
422
+ return joinOnTarget;
423
+ }
417
424
  return upstream;
418
425
  }
419
426
  return upstream;
@@ -433,7 +440,7 @@ class StaticPredicatePlacementOptimizer {
433
440
  }
434
441
  return null;
435
442
  }
436
- resolveTargetPlacement(query, targetColumns) {
443
+ resolveTargetPlacement(contextRoot, query, targetColumns) {
437
444
  const hasOrdinaryDistinct = this.hasOrdinaryDistinct(query);
438
445
  if (this.hasDistinctOnBoundary(query)) {
439
446
  return {
@@ -453,11 +460,11 @@ class StaticPredicatePlacementOptimizer {
453
460
  reason: "Predicate crosses LIMIT/OFFSET/FETCH boundary; moving it may change row selection semantics."
454
461
  };
455
462
  }
456
- if (query.fromClause && this.hasOuterJoin(query.fromClause)) {
457
- return {
458
- code: "OUTER_JOIN_BOUNDARY",
459
- reason: "Target query contains an OUTER JOIN boundary that is not moved across in the safe-only implementation."
460
- };
463
+ if (query.fromClause) {
464
+ const nullableSide = this.findNullableSideBoundaryForTargetColumns(contextRoot, query, targetColumns);
465
+ if (nullableSide) {
466
+ return nullableSide;
467
+ }
461
468
  }
462
469
  if (query.groupByClause) {
463
470
  const allReferencesAreGroupKeys = targetColumns.every(item => this.isGroupKeyColumn(query, item.targetColumn));
@@ -486,6 +493,22 @@ class StaticPredicatePlacementOptimizer {
486
493
  reason: "All outer references resolve to direct upstream outputs before unsafe query boundaries."
487
494
  };
488
495
  }
496
+ findNullableSideBoundaryForTargetColumns(contextRoot, query, targetColumns) {
497
+ if (!query.fromClause) {
498
+ return null;
499
+ }
500
+ for (const item of targetColumns) {
501
+ const binding = this.resolveSourceBinding(contextRoot, query, item.targetColumn);
502
+ if ("code" in binding) {
503
+ return binding;
504
+ }
505
+ const nullableSide = this.findNullableSideBoundary(query.fromClause, binding);
506
+ if (nullableSide) {
507
+ return nullableSide;
508
+ }
509
+ }
510
+ return null;
511
+ }
489
512
  resolveSourceBinding(contextRoot, query, column) {
490
513
  const fromClause = query.fromClause;
491
514
  if (!fromClause) {
@@ -495,10 +518,10 @@ class StaticPredicatePlacementOptimizer {
495
518
  };
496
519
  }
497
520
  const bindings = this.getSourceBindings(fromClause);
498
- const namespace = normalizeIdentifier(column.getNamespace());
521
+ const namespace = column.getNamespace();
499
522
  const columnName = column.column.name;
500
523
  if (namespace) {
501
- const matches = bindings.filter(binding => normalizeIdentifier(binding.alias) === namespace);
524
+ const matches = bindings.filter(binding => (0, PredicateExpressionUtils_1.identifiersEqual)(binding.alias, namespace));
502
525
  if (matches.length !== 1) {
503
526
  return {
504
527
  code: "AMBIGUOUS_COLUMN_SOURCE",
@@ -506,16 +529,19 @@ class StaticPredicatePlacementOptimizer {
506
529
  };
507
530
  }
508
531
  const matchCount = this.getOutputColumnMatchCount(contextRoot, matches[0], columnName);
532
+ if (matchCount === 0 && this.isBaseTableBinding(contextRoot, matches[0])) {
533
+ return matches[0];
534
+ }
509
535
  if (matchCount === 0) {
510
536
  return {
511
537
  code: "COLUMN_NOT_AVAILABLE_UPSTREAM",
512
- reason: `Column '${columnReferenceText(column)}' is not a direct output of the referenced source.`
538
+ reason: `Column '${(0, PredicateExpressionUtils_1.columnReferenceText)(column)}' is not a direct output of the referenced source.`
513
539
  };
514
540
  }
515
541
  if (matchCount > 1) {
516
542
  return {
517
543
  code: "AMBIGUOUS_COLUMN_REFERENCE",
518
- reason: `Column '${columnReferenceText(column)}' resolves to multiple outputs in the referenced source.`
544
+ reason: `Column '${(0, PredicateExpressionUtils_1.columnReferenceText)(column)}' resolves to multiple outputs in the referenced source.`
519
545
  };
520
546
  }
521
547
  return matches[0];
@@ -634,7 +660,7 @@ class StaticPredicatePlacementOptimizer {
634
660
  resolveTargetColumns(root, query, references) {
635
661
  const resolved = [];
636
662
  for (const reference of references) {
637
- const matches = this.collectSelectOutputs(root, query).filter(item => normalizeIdentifier(item.name) === normalizeIdentifier(reference.column.name));
663
+ const matches = this.collectDirectOutputMatches(root, query, reference.column.name);
638
664
  if (matches.length !== 1) {
639
665
  return {
640
666
  code: "AMBIGUOUS_TARGET_COLUMN",
@@ -661,6 +687,57 @@ class StaticPredicatePlacementOptimizer {
661
687
  }
662
688
  return resolved;
663
689
  }
690
+ resolveBaseTableJoinOnTarget(contextRoot, currentQuery, binding) {
691
+ var _a, _b, _c;
692
+ if (!currentQuery.fromClause || !this.isBaseTableBinding(contextRoot, binding)) {
693
+ return {
694
+ code: "NO_SAFE_UPSTREAM_QUERY",
695
+ reason: "The referenced source is a base table, so there is no upstream query block to move into."
696
+ };
697
+ }
698
+ if (this.hasOuterJoin(currentQuery.fromClause)) {
699
+ return {
700
+ code: "OUTER_JOIN_BOUNDARY",
701
+ reason: "Predicate crosses an OUTER JOIN boundary; moving it into JOIN ON may change semantics."
702
+ };
703
+ }
704
+ const join = binding.isPrimary
705
+ ? (_b = (_a = currentQuery.fromClause.joins) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : null
706
+ : binding.join;
707
+ if (join === null || join === void 0 ? void 0 : join.lateral) {
708
+ return {
709
+ code: "LATERAL_JOIN_BOUNDARY",
710
+ reason: "Predicate crosses a LATERAL JOIN boundary; moving it into JOIN ON may change semantics."
711
+ };
712
+ }
713
+ if (!join || !this.isInnerJoin(join)) {
714
+ return {
715
+ code: "NO_SAFE_JOIN_ON_TARGET",
716
+ reason: "Base-table predicates are moved only into INNER JOIN ON clauses in the safe-only implementation."
717
+ };
718
+ }
719
+ if (!(join.condition instanceof Clause_1.JoinOnClause)) {
720
+ return {
721
+ code: "NO_SAFE_JOIN_ON_TARGET",
722
+ reason: "Base-table predicates are moved only into existing JOIN ON clauses, not USING or conditionless joins."
723
+ };
724
+ }
725
+ return {
726
+ kind: "join_on",
727
+ join,
728
+ scopeId: `join_on:${(_c = join.source.getAliasName()) !== null && _c !== void 0 ? _c : "unknown"}`,
729
+ reason: binding.isPrimary
730
+ ? "Predicate references the primary source of an INNER JOIN; it is moved into the first JOIN ON clause."
731
+ : "Predicate references the joined source of an INNER JOIN; it is moved into that JOIN ON clause."
732
+ };
733
+ }
734
+ appendJoinOnPredicate(join, expression, options) {
735
+ if (!(join.condition instanceof Clause_1.JoinOnClause)) {
736
+ return;
737
+ }
738
+ join.condition.condition = new ValueComponent_1.BinaryExpression(join.condition.condition, "and", (0, PredicateExpressionUtils_1.cloneValueComponent)(expression, options));
739
+ join.condition.condition = (0, TopLevelAndConditionDeduper_1.dedupeTopLevelAndConditions)(join.condition.condition, options);
740
+ }
664
741
  resolveDeepestBranchTarget(contextRoot, query, targetColumns, visited = new Set()) {
665
742
  if (visited.has(query) || targetColumns.length === 0) {
666
743
  return { query, targetColumns: [...targetColumns] };
@@ -688,14 +765,17 @@ class StaticPredicatePlacementOptimizer {
688
765
  return { query, targetColumns: [...targetColumns] };
689
766
  }
690
767
  const upstream = this.resolveUpstreamQuery(contextRoot, binding, targetColumns.map(item => item.targetColumn));
691
- if ("code" in upstream || upstream.kind === "union") {
768
+ if ("code" in upstream) {
769
+ return { query, targetColumns: [...targetColumns] };
770
+ }
771
+ if (upstream.kind !== "simple") {
692
772
  return { query, targetColumns: [...targetColumns] };
693
773
  }
694
774
  const upstreamColumns = this.resolveTargetColumns(contextRoot, upstream.query, targetColumns.map(item => item.targetColumn));
695
775
  if ("code" in upstreamColumns) {
696
776
  return { query, targetColumns: [...targetColumns] };
697
777
  }
698
- const placement = this.resolveTargetPlacement(upstream.query, upstreamColumns);
778
+ const placement = this.resolveTargetPlacement(contextRoot, upstream.query, upstreamColumns);
699
779
  if ("code" in placement) {
700
780
  return { query, targetColumns: [...targetColumns] };
701
781
  }
@@ -713,14 +793,14 @@ class StaticPredicatePlacementOptimizer {
713
793
  };
714
794
  }
715
795
  const bindings = this.getSourceBindings(query.fromClause);
716
- const namespace = normalizeIdentifier(column.getNamespace());
796
+ const namespace = column.getNamespace();
717
797
  if (namespace) {
718
- const matches = bindings.filter(binding => normalizeIdentifier(binding.alias) === namespace);
798
+ const matches = bindings.filter(binding => (0, PredicateExpressionUtils_1.identifiersEqual)(binding.alias, namespace));
719
799
  return matches.length === 1
720
800
  ? null
721
801
  : {
722
802
  code: "AMBIGUOUS_TARGET_COLUMN",
723
- reason: `Target column '${columnReferenceText(column)}' is not uniquely resolvable in the destination query.`
803
+ reason: `Target column '${(0, PredicateExpressionUtils_1.columnReferenceText)(column)}' is not uniquely resolvable in the destination query.`
724
804
  };
725
805
  }
726
806
  if (bindings.length === 1) {
@@ -737,16 +817,16 @@ class StaticPredicatePlacementOptimizer {
737
817
  return false;
738
818
  }
739
819
  return groupBy.grouping.some(grouping => {
740
- const candidate = unwrapParens(grouping);
820
+ const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(grouping);
741
821
  return candidate instanceof ValueComponent_1.ColumnReference
742
822
  && this.sameResolvableColumnInQuery(query, candidate, column);
743
823
  });
744
824
  }
745
825
  sameResolvableColumnInQuery(query, left, right) {
746
- if (sameColumnReference(left, right)) {
826
+ if ((0, PredicateExpressionUtils_1.sameColumnReference)(left, right)) {
747
827
  return true;
748
828
  }
749
- if (normalizeIdentifier(left.column.name) !== normalizeIdentifier(right.column.name)) {
829
+ if (!(0, PredicateExpressionUtils_1.identifiersEqual)(left.column.name, right.column.name)) {
750
830
  return false;
751
831
  }
752
832
  const leftSource = this.resolveColumnSourceAlias(query, left);
@@ -758,15 +838,15 @@ class StaticPredicatePlacementOptimizer {
758
838
  return null;
759
839
  }
760
840
  const bindings = this.getSourceBindings(query.fromClause);
761
- const namespace = normalizeIdentifier(column.getNamespace());
841
+ const namespace = column.getNamespace();
762
842
  if (namespace) {
763
- const matches = bindings.filter(binding => normalizeIdentifier(binding.alias) === namespace);
764
- return matches.length === 1 ? normalizeIdentifier(matches[0].alias) : null;
843
+ const matches = bindings.filter(binding => (0, PredicateExpressionUtils_1.identifiersEqual)(binding.alias, namespace));
844
+ return matches.length === 1 ? matches[0].alias : null;
765
845
  }
766
- return bindings.length === 1 ? normalizeIdentifier(bindings[0].alias) : null;
846
+ return bindings.length === 1 ? bindings[0].alias : null;
767
847
  }
768
848
  rebasePredicate(expression, targetColumns, options) {
769
- const cloned = cloneValueComponent(expression, options);
849
+ const cloned = (0, PredicateExpressionUtils_1.cloneValueComponent)(expression, options);
770
850
  const visitSelect = (query, inheritedLocalAliases) => {
771
851
  var _a, _b, _c, _d;
772
852
  if (!(query instanceof SelectQuery_1.SimpleSelectQuery)) {
@@ -799,15 +879,15 @@ class StaticPredicatePlacementOptimizer {
799
879
  }
800
880
  };
801
881
  const visit = (value, localAliases) => {
802
- const candidate = unwrapParens(value);
882
+ const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(value);
803
883
  if (candidate instanceof ValueComponent_1.ColumnReference) {
804
- const namespace = normalizeIdentifier(candidate.getNamespace());
884
+ const namespace = (0, PredicateExpressionUtils_1.normalizeIdentifier)(candidate.getNamespace());
805
885
  if (namespace && localAliases.has(namespace)) {
806
886
  return;
807
887
  }
808
- const target = targetColumns.find(item => sameColumnReference(candidate, item.sourceColumn));
888
+ const target = targetColumns.find(item => (0, PredicateExpressionUtils_1.sameColumnReference)(candidate, item.sourceColumn));
809
889
  if (target) {
810
- candidate.qualifiedName = cloneColumnReference(target.targetColumn).qualifiedName;
890
+ candidate.qualifiedName = (0, PredicateExpressionUtils_1.cloneColumnReference)(target.targetColumn).qualifiedName;
811
891
  }
812
892
  return;
813
893
  }
@@ -900,6 +980,14 @@ class StaticPredicatePlacementOptimizer {
900
980
  }
901
981
  return bindings;
902
982
  }
983
+ isBaseTableBinding(root, binding) {
984
+ const source = binding.source.datasource;
985
+ return source instanceof Clause_1.TableSource && !this.findCte(root, source.table.name);
986
+ }
987
+ isInnerJoin(join) {
988
+ const joinType = join.joinType.value.trim().toLowerCase();
989
+ return joinType === "join" || joinType === "inner join";
990
+ }
903
991
  findNullableSideBoundary(fromClause, binding) {
904
992
  var _a;
905
993
  const joins = (_a = fromClause.joins) !== null && _a !== void 0 ? _a : [];
@@ -967,12 +1055,12 @@ class StaticPredicatePlacementOptimizer {
967
1055
  if ("code" in branches) {
968
1056
  return 0;
969
1057
  }
970
- return this.collectSelectOutputs(root, branches[0]).filter(item => normalizeIdentifier(item.name) === normalizeIdentifier(columnName)).length;
1058
+ return this.collectDirectOutputMatches(root, branches[0], columnName).length;
971
1059
  }
972
1060
  if (!(target instanceof SelectQuery_1.SimpleSelectQuery)) {
973
1061
  return 0;
974
1062
  }
975
- return this.collectSelectOutputs(root, target).filter(item => normalizeIdentifier(item.name) === normalizeIdentifier(columnName)).length;
1063
+ return this.collectDirectOutputMatches(root, target, columnName).length;
976
1064
  }
977
1065
  collectUnionBranches(query) {
978
1066
  const branches = [];
@@ -1003,7 +1091,7 @@ class StaticPredicatePlacementOptimizer {
1003
1091
  resolveUnionOutputIndex(root, firstBranch, outputColumnName) {
1004
1092
  const matches = [];
1005
1093
  this.collectSelectOutputs(root, firstBranch).forEach((item, index) => {
1006
- if (normalizeIdentifier(item.name) === normalizeIdentifier(outputColumnName)) {
1094
+ if ((0, PredicateExpressionUtils_1.identifiersEqual)(item.name, outputColumnName)) {
1007
1095
  matches.push(index);
1008
1096
  }
1009
1097
  });
@@ -1025,6 +1113,15 @@ class StaticPredicatePlacementOptimizer {
1025
1113
  reason: `UNION branch does not expose column '${sourceColumn.column.name}' at output position ${outputIndex + 1}.`
1026
1114
  };
1027
1115
  }
1116
+ else if ((0, PredicateExpressionUtils_1.identifiersEqual)(output.name, sourceColumn.column.name)) {
1117
+ const matches = this.collectDirectOutputMatches(root, query, sourceColumn.column.name);
1118
+ if (matches.length > 1) {
1119
+ return {
1120
+ code: "AMBIGUOUS_TARGET_COLUMN",
1121
+ reason: `UNION branch exposes multiple '${sourceColumn.column.name}' columns.`
1122
+ };
1123
+ }
1124
+ }
1028
1125
  if (!(output.value instanceof ValueComponent_1.ColumnReference)) {
1029
1126
  return {
1030
1127
  code: "EXPRESSION_OUTPUT_UNSUPPORTED",
@@ -1049,6 +1146,96 @@ class StaticPredicatePlacementOptimizer {
1049
1146
  const collector = new SelectOutputCollector_1.SelectOutputCollector(null, commonTables.length > 0 ? commonTables : null);
1050
1147
  return collector.collect(query);
1051
1148
  }
1149
+ collectDirectOutputMatches(root, query, columnName) {
1150
+ const collectedMatches = this.collectSelectOutputs(root, query).filter(item => (0, PredicateExpressionUtils_1.identifiersEqual)(item.name, columnName));
1151
+ if (collectedMatches.length > 0) {
1152
+ return this.hasExplicitOutputMatch(query, columnName)
1153
+ ? [...collectedMatches, ...this.inferWildcardOutputMatches(root, query, columnName, true)]
1154
+ : collectedMatches;
1155
+ }
1156
+ return this.inferWildcardOutputMatches(root, query, columnName, false);
1157
+ }
1158
+ hasExplicitOutputMatch(query, columnName) {
1159
+ return query.selectClause.items.some(item => {
1160
+ if (item.identifier) {
1161
+ return (0, PredicateExpressionUtils_1.identifiersEqual)(item.identifier.name, columnName);
1162
+ }
1163
+ return item.value instanceof ValueComponent_1.ColumnReference
1164
+ && item.value.column.name !== "*"
1165
+ && (0, PredicateExpressionUtils_1.identifiersEqual)(item.value.column.name, columnName);
1166
+ });
1167
+ }
1168
+ inferWildcardOutputMatches(root, query, columnName, includeUnprovenPotential) {
1169
+ const matches = [];
1170
+ query.selectClause.items.forEach((item, outputIndex) => {
1171
+ if (item.identifier || !(item.value instanceof ValueComponent_1.ColumnReference) || item.value.column.name !== "*") {
1172
+ return;
1173
+ }
1174
+ const targetColumn = this.inferWildcardTargetColumn(root, query, item.value, columnName, includeUnprovenPotential);
1175
+ if (!targetColumn) {
1176
+ return;
1177
+ }
1178
+ if (targetColumn === "ambiguous") {
1179
+ matches.push(this.createInferredOutputColumn(columnName, new ValueComponent_1.ColumnReference(null, columnName), outputIndex), this.createInferredOutputColumn(columnName, new ValueComponent_1.ColumnReference(null, columnName), outputIndex));
1180
+ return;
1181
+ }
1182
+ matches.push(this.createInferredOutputColumn(columnName, targetColumn, outputIndex));
1183
+ });
1184
+ return matches;
1185
+ }
1186
+ inferWildcardTargetColumn(root, query, wildcard, columnName, includeUnprovenPotential) {
1187
+ if (!query.fromClause) {
1188
+ return null;
1189
+ }
1190
+ const bindings = this.getSourceBindings(query.fromClause);
1191
+ if (wildcard.namespaces === null) {
1192
+ if (bindings.length !== 1) {
1193
+ return "ambiguous";
1194
+ }
1195
+ return this.resolveWildcardColumnFromBinding(root, query, bindings[0], columnName, includeUnprovenPotential);
1196
+ }
1197
+ const namespace = wildcard.getNamespace();
1198
+ const matches = bindings.filter(binding => (0, PredicateExpressionUtils_1.identifiersEqual)(binding.alias, namespace));
1199
+ if (matches.length === 0) {
1200
+ return null;
1201
+ }
1202
+ return matches.length === 1
1203
+ ? this.resolveWildcardColumnFromBinding(root, query, matches[0], columnName, includeUnprovenPotential)
1204
+ : "ambiguous";
1205
+ }
1206
+ resolveWildcardColumnFromBinding(root, query, binding, columnName, includeUnprovenPotential) {
1207
+ const matches = this.collectSourceOutputMatches(root, query, binding.source, columnName);
1208
+ if (matches.length > 1) {
1209
+ return "ambiguous";
1210
+ }
1211
+ if (matches.length === 1) {
1212
+ const value = matches[0].value;
1213
+ return value instanceof ValueComponent_1.ColumnReference ? value : null;
1214
+ }
1215
+ return includeUnprovenPotential ? this.createColumnForBinding(binding, columnName) : null;
1216
+ }
1217
+ collectSourceOutputMatches(root, query, source, columnName) {
1218
+ var _a, _b, _c, _d;
1219
+ const commonTables = [
1220
+ ...((_b = (_a = query.withClause) === null || _a === void 0 ? void 0 : _a.tables) !== null && _b !== void 0 ? _b : []),
1221
+ ...((_d = (_c = root.withClause) === null || _c === void 0 ? void 0 : _c.tables) !== null && _d !== void 0 ? _d : [])
1222
+ ];
1223
+ const collector = new SelectOutputCollector_1.SelectOutputCollector(null, commonTables.length > 0 ? commonTables : null);
1224
+ return collector.collect(source).filter(item => (0, PredicateExpressionUtils_1.identifiersEqual)(item.name, columnName));
1225
+ }
1226
+ createColumnForBinding(binding, columnName) {
1227
+ return new ValueComponent_1.ColumnReference(binding.alias ? [binding.alias] : null, columnName);
1228
+ }
1229
+ createInferredOutputColumn(name, value, outputIndex) {
1230
+ return {
1231
+ name,
1232
+ value,
1233
+ outputIndex,
1234
+ sourceAlias: value.getNamespace() || null,
1235
+ sourceName: null,
1236
+ sourceColumnName: name
1237
+ };
1238
+ }
1052
1239
  resolveSourceQueryForColumns(root, source) {
1053
1240
  var _a;
1054
1241
  if (source.datasource instanceof Clause_1.SubQuerySource) {
@@ -1064,13 +1251,13 @@ class StaticPredicatePlacementOptimizer {
1064
1251
  }
1065
1252
  findCte(root, name) {
1066
1253
  var _a, _b;
1067
- const normalized = normalizeIdentifier(name);
1254
+ const normalized = (0, PredicateExpressionUtils_1.normalizeIdentifier)(name);
1068
1255
  const matches = ((_b = (_a = root.withClause) === null || _a === void 0 ? void 0 : _a.tables) !== null && _b !== void 0 ? _b : [])
1069
- .filter(table => normalizeIdentifier(table.getSourceAliasName()) === normalized);
1256
+ .filter(table => (0, PredicateExpressionUtils_1.normalizeIdentifier)(table.getSourceAliasName()) === normalized);
1070
1257
  return matches.length === 1 ? matches[0] : null;
1071
1258
  }
1072
1259
  countTableSourceReferences(query, tableName) {
1073
- const normalized = normalizeIdentifier(tableName);
1260
+ const normalized = (0, PredicateExpressionUtils_1.normalizeIdentifier)(tableName);
1074
1261
  let count = 0;
1075
1262
  const visitSelect = (select) => {
1076
1263
  var _a, _b;
@@ -1085,7 +1272,7 @@ class StaticPredicatePlacementOptimizer {
1085
1272
  if (select.fromClause) {
1086
1273
  for (const binding of this.getSourceBindings(select.fromClause)) {
1087
1274
  const source = binding.source.datasource;
1088
- if (source instanceof Clause_1.TableSource && normalizeIdentifier(source.table.name) === normalized) {
1275
+ if (source instanceof Clause_1.TableSource && (0, PredicateExpressionUtils_1.normalizeIdentifier)(source.table.name) === normalized) {
1089
1276
  count += 1;
1090
1277
  }
1091
1278
  if (source instanceof Clause_1.SubQuerySource) {
@@ -1108,7 +1295,7 @@ class StaticPredicatePlacementOptimizer {
1108
1295
  for (const source of (_b = (_a = query.fromClause) === null || _a === void 0 ? void 0 : _a.getSources()) !== null && _b !== void 0 ? _b : []) {
1109
1296
  const alias = source.getAliasName();
1110
1297
  if (alias) {
1111
- aliases.add(normalizeIdentifier(alias));
1298
+ aliases.add((0, PredicateExpressionUtils_1.normalizeIdentifier)(alias));
1112
1299
  }
1113
1300
  }
1114
1301
  return aliases;
@@ -1148,14 +1335,14 @@ class StaticPredicatePlacementOptimizer {
1148
1335
  }
1149
1336
  };
1150
1337
  const collect = (reference) => {
1151
- if (!references.some(existing => sameColumnReference(existing, reference))) {
1338
+ if (!references.some(existing => (0, PredicateExpressionUtils_1.sameColumnReference)(existing, reference))) {
1152
1339
  references.push(reference);
1153
1340
  }
1154
1341
  };
1155
1342
  const visit = (value, localAliases) => {
1156
- const candidate = unwrapParens(value);
1343
+ const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(value);
1157
1344
  if (candidate instanceof ValueComponent_1.ColumnReference) {
1158
- const namespace = normalizeIdentifier(candidate.getNamespace());
1345
+ const namespace = (0, PredicateExpressionUtils_1.normalizeIdentifier)(candidate.getNamespace());
1159
1346
  if (namespace) {
1160
1347
  if (rootAliases.has(namespace) && !localAliases.has(namespace)) {
1161
1348
  collect(candidate);
@@ -1248,7 +1435,7 @@ class StaticPredicatePlacementOptimizer {
1248
1435
  if (found) {
1249
1436
  return;
1250
1437
  }
1251
- const candidate = unwrapParens(value);
1438
+ const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(value);
1252
1439
  if (candidate instanceof ValueComponent_1.FunctionCall) {
1253
1440
  if (candidate.over) {
1254
1441
  found = true;
@@ -1330,9 +1517,9 @@ class StaticPredicatePlacementOptimizer {
1330
1517
  }
1331
1518
  };
1332
1519
  const visit = (value) => {
1333
- const candidate = unwrapParens(value);
1520
+ const candidate = (0, PredicateExpressionUtils_1.unwrapParens)(value);
1334
1521
  if (candidate instanceof ValueComponent_1.ParameterExpression) {
1335
- appendUnique(names, candidate.name.value);
1522
+ (0, PredicateExpressionUtils_1.appendUnique)(names, candidate.name.value);
1336
1523
  return;
1337
1524
  }
1338
1525
  if (candidate instanceof ValueComponent_1.BinaryExpression) {
@@ -1407,10 +1594,10 @@ class StaticPredicatePlacementOptimizer {
1407
1594
  visit(expression);
1408
1595
  return names;
1409
1596
  }
1410
- makeSkipped(expression, draft, options) {
1597
+ makeSkipped(expression, scopeId, draft, options) {
1411
1598
  return {
1412
1599
  predicateSql: (0, SqlComponentFormatter_1.formatSqlComponent)(expression, options),
1413
- scopeId: "scope:root",
1600
+ scopeId,
1414
1601
  ...draft
1415
1602
  };
1416
1603
  }