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