rawsql-ts 0.27.0 → 0.29.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.min.js +1 -1
- package/dist/esm/index.min.js.map +4 -4
- package/dist/esm/transformers/ConditionDeduplicationOptimizer.d.ts +22 -0
- package/dist/esm/transformers/ConditionDeduplicationOptimizer.js +198 -0
- package/dist/esm/transformers/ConditionDeduplicationOptimizer.js.map +1 -0
- package/dist/esm/transformers/ConditionOptimization.d.ts +35 -1
- package/dist/esm/transformers/ConditionOptimization.js +359 -24
- package/dist/esm/transformers/ConditionOptimization.js.map +1 -1
- package/dist/esm/transformers/ParameterConditionPlacementOptimizer.d.ts +13 -0
- package/dist/esm/transformers/ParameterConditionPlacementOptimizer.js +246 -27
- package/dist/esm/transformers/ParameterConditionPlacementOptimizer.js.map +1 -1
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.d.ts +12 -0
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.js +211 -18
- package/dist/esm/transformers/StaticPredicatePlacementOptimizer.js.map +1 -1
- package/dist/esm/transformers/TopLevelAndConditionDeduper.d.ts +10 -0
- package/dist/esm/transformers/TopLevelAndConditionDeduper.js +66 -0
- package/dist/esm/transformers/TopLevelAndConditionDeduper.js.map +1 -0
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +4 -4
- package/dist/src/transformers/ConditionDeduplicationOptimizer.d.ts +22 -0
- package/dist/src/transformers/ConditionOptimization.d.ts +35 -1
- package/dist/src/transformers/ParameterConditionPlacementOptimizer.d.ts +13 -0
- package/dist/src/transformers/StaticPredicatePlacementOptimizer.d.ts +12 -0
- package/dist/src/transformers/TopLevelAndConditionDeduper.d.ts +10 -0
- package/dist/transformers/ConditionDeduplicationOptimizer.js +202 -0
- package/dist/transformers/ConditionDeduplicationOptimizer.js.map +1 -0
- package/dist/transformers/ConditionOptimization.js +362 -22
- package/dist/transformers/ConditionOptimization.js.map +1 -1
- package/dist/transformers/ParameterConditionPlacementOptimizer.js +245 -26
- package/dist/transformers/ParameterConditionPlacementOptimizer.js.map +1 -1
- package/dist/transformers/StaticPredicatePlacementOptimizer.js +210 -17
- package/dist/transformers/StaticPredicatePlacementOptimizer.js.map +1 -1
- package/dist/transformers/TopLevelAndConditionDeduper.js +72 -0
- package/dist/transformers/TopLevelAndConditionDeduper.js.map +1 -0
- package/dist/tsconfig.browser.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import { DistinctOn, SubQuerySource, TableSource, WhereClause } from "../models/Clause";
|
|
1
|
+
import { DistinctOn, JoinOnClause, SubQuerySource, TableSource, WhereClause } from "../models/Clause";
|
|
2
2
|
import { BinarySelectQuery, SimpleSelectQuery } from "../models/SelectQuery";
|
|
3
3
|
import { ArrayExpression, ArrayQueryExpression, BetweenExpression, BinaryExpression, CaseExpression, CastExpression, ColumnReference, FunctionCall, InlineQuery, JsonPredicateExpression, ParameterExpression, ParenExpression, TupleExpression, TypeValue, UnaryExpression, ValueList } from "../models/ValueComponent";
|
|
4
4
|
import { SelectQueryParser } from "../parsers/SelectQueryParser";
|
|
5
5
|
import { ValueParser } from "../parsers/ValueParser";
|
|
6
6
|
import { formatSqlComponent, hasSqlComponentFormatOverride } from "./SqlComponentFormatter";
|
|
7
7
|
import { SelectOutputCollector } from "./SelectOutputCollector";
|
|
8
|
+
import { dedupeTopLevelAndConditions, dedupeWhereTopLevelAndConditions } from "./TopLevelAndConditionDeduper";
|
|
8
9
|
const SUPPORTED_OPERATORS = new Set(["=", "<>", "!=", "<", "<=", ">", ">=", "like", "ilike", "in"]);
|
|
9
10
|
const VOLATILE_OR_UNSUPPORTED_FUNCTION_REASON = "Condition contains a function call; volatile and expression predicates are not moved in the safe-only implementation.";
|
|
10
11
|
const normalizeIdentifier = (value) => value.trim().toLowerCase();
|
|
12
|
+
const isCaseSensitiveIdentifier = (value) => /[A-Z]/.test(value.trim());
|
|
13
|
+
const identifiersEqual = (left, right) => {
|
|
14
|
+
const trimmedLeft = left.trim();
|
|
15
|
+
const trimmedRight = right.trim();
|
|
16
|
+
return isCaseSensitiveIdentifier(trimmedLeft) || isCaseSensitiveIdentifier(trimmedRight)
|
|
17
|
+
? trimmedLeft === trimmedRight
|
|
18
|
+
: trimmedLeft.toLowerCase() === trimmedRight.toLowerCase();
|
|
19
|
+
};
|
|
11
20
|
const unwrapParens = (expression) => {
|
|
12
21
|
let candidate = expression;
|
|
13
22
|
while (candidate instanceof ParenExpression) {
|
|
@@ -59,8 +68,8 @@ const columnReferenceText = (reference) => {
|
|
|
59
68
|
return namespace ? `${namespace}.${reference.column.name}` : reference.column.name;
|
|
60
69
|
};
|
|
61
70
|
const sameColumnReference = (left, right) => {
|
|
62
|
-
return
|
|
63
|
-
&&
|
|
71
|
+
return identifiersEqual(left.column.name, right.column.name)
|
|
72
|
+
&& identifiersEqual(left.getNamespace(), right.getNamespace());
|
|
64
73
|
};
|
|
65
74
|
const appendUnique = (items, value) => {
|
|
66
75
|
if (!items.includes(value)) {
|
|
@@ -120,7 +129,11 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
120
129
|
continue;
|
|
121
130
|
}
|
|
122
131
|
let appliedReason = "";
|
|
123
|
-
if (target.kind === "
|
|
132
|
+
if (target.kind === "join_on") {
|
|
133
|
+
this.appendJoinOnCondition(target.join, candidate.expression, options);
|
|
134
|
+
appliedReason = target.reason;
|
|
135
|
+
}
|
|
136
|
+
else if (target.kind === "simple") {
|
|
124
137
|
const targetColumns = this.resolveTargetColumns(query, target.query, candidate.references);
|
|
125
138
|
if ("code" in targetColumns) {
|
|
126
139
|
skipped.push(this.makeSkipped(term, targetColumns, options));
|
|
@@ -133,6 +146,7 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
133
146
|
}
|
|
134
147
|
const movedCondition = this.rebaseCondition(candidate.expression, targetColumns, options);
|
|
135
148
|
target.query.appendWhere(movedCondition);
|
|
149
|
+
dedupeWhereTopLevelAndConditions(target.query, options);
|
|
136
150
|
appliedReason = placement.reason;
|
|
137
151
|
}
|
|
138
152
|
else {
|
|
@@ -153,6 +167,7 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
153
167
|
for (const branch of target.branches) {
|
|
154
168
|
const movedCondition = this.rebaseCondition(candidate.expression, branch.targetColumns, options);
|
|
155
169
|
branch.query.appendWhere(movedCondition);
|
|
170
|
+
dedupeWhereTopLevelAndConditions(branch.query, options);
|
|
156
171
|
}
|
|
157
172
|
appliedReason = placements.some(item => /group by/i.test(item.reason))
|
|
158
173
|
? "Condition is distributed to every UNION branch by output column position; grouped branches only receive GROUP BY-key predicates."
|
|
@@ -351,9 +366,15 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
351
366
|
}
|
|
352
367
|
return null;
|
|
353
368
|
}
|
|
369
|
+
if (candidate instanceof UnaryExpression) {
|
|
370
|
+
const operator = candidate.operator.value.trim().toLowerCase();
|
|
371
|
+
if (operator === "not") {
|
|
372
|
+
return visit(candidate.expression);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
354
375
|
return {
|
|
355
376
|
code: "UNSUPPORTED_PARAMETER_CONDITION",
|
|
356
|
-
reason: "Only simple binary, BETWEEN, and whole OR/AND parameter predicates are moved in the safe-only implementation."
|
|
377
|
+
reason: "Only simple binary, BETWEEN, and whole OR/AND/NOT parameter predicates are moved in the safe-only implementation."
|
|
357
378
|
};
|
|
358
379
|
};
|
|
359
380
|
return visit(expression);
|
|
@@ -374,6 +395,7 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
374
395
|
&& right.values.every(value => unwrapParens(value) instanceof ParameterExpression);
|
|
375
396
|
}
|
|
376
397
|
resolveTarget(root, candidate) {
|
|
398
|
+
var _a;
|
|
377
399
|
const boundary = this.findRootQueryBoundary(root);
|
|
378
400
|
if (boundary) {
|
|
379
401
|
return boundary;
|
|
@@ -401,12 +423,25 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
401
423
|
};
|
|
402
424
|
}
|
|
403
425
|
const binding = bindings[0];
|
|
426
|
+
if ((_a = binding.join) === null || _a === void 0 ? void 0 : _a.lateral) {
|
|
427
|
+
return {
|
|
428
|
+
code: "LATERAL_JOIN_BOUNDARY",
|
|
429
|
+
reason: "Condition crosses a LATERAL JOIN boundary; moving it may change semantics."
|
|
430
|
+
};
|
|
431
|
+
}
|
|
404
432
|
const nullableSide = this.findNullableSideBoundary(root.fromClause, binding);
|
|
405
433
|
if (nullableSide) {
|
|
406
434
|
return nullableSide;
|
|
407
435
|
}
|
|
408
436
|
const upstream = this.resolveUpstreamQuery(root, binding, candidate.references);
|
|
409
437
|
if ("code" in upstream) {
|
|
438
|
+
if (upstream.code === "NO_SAFE_UPSTREAM_QUERY") {
|
|
439
|
+
const joinOnTarget = this.resolveBaseTableJoinOnTarget(root, binding);
|
|
440
|
+
if (!("code" in joinOnTarget)) {
|
|
441
|
+
return joinOnTarget;
|
|
442
|
+
}
|
|
443
|
+
return joinOnTarget;
|
|
444
|
+
}
|
|
410
445
|
return upstream;
|
|
411
446
|
}
|
|
412
447
|
return upstream;
|
|
@@ -488,10 +523,10 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
488
523
|
};
|
|
489
524
|
}
|
|
490
525
|
const bindings = this.getSourceBindings(fromClause);
|
|
491
|
-
const namespace =
|
|
526
|
+
const namespace = column.getNamespace();
|
|
492
527
|
const columnName = column.column.name;
|
|
493
528
|
if (namespace) {
|
|
494
|
-
const matches = bindings.filter(binding =>
|
|
529
|
+
const matches = bindings.filter(binding => identifiersEqual(binding.alias, namespace));
|
|
495
530
|
if (matches.length !== 1) {
|
|
496
531
|
return {
|
|
497
532
|
code: "AMBIGUOUS_COLUMN_SOURCE",
|
|
@@ -499,6 +534,9 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
499
534
|
};
|
|
500
535
|
}
|
|
501
536
|
const matchCount = this.getOutputColumnMatchCount(contextRoot, matches[0], columnName);
|
|
537
|
+
if (matchCount === 0 && this.isBaseTableBinding(contextRoot, matches[0])) {
|
|
538
|
+
return matches[0];
|
|
539
|
+
}
|
|
502
540
|
if (matchCount === 0) {
|
|
503
541
|
return {
|
|
504
542
|
code: "COLUMN_NOT_AVAILABLE_UPSTREAM",
|
|
@@ -627,7 +665,7 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
627
665
|
resolveTargetColumns(root, query, references) {
|
|
628
666
|
const resolved = [];
|
|
629
667
|
for (const reference of references) {
|
|
630
|
-
const matches = this.
|
|
668
|
+
const matches = this.collectDirectOutputMatches(root, query, reference.column.name);
|
|
631
669
|
if (matches.length !== 1) {
|
|
632
670
|
return {
|
|
633
671
|
code: "AMBIGUOUS_TARGET_COLUMN",
|
|
@@ -654,6 +692,57 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
654
692
|
}
|
|
655
693
|
return resolved;
|
|
656
694
|
}
|
|
695
|
+
resolveBaseTableJoinOnTarget(root, binding) {
|
|
696
|
+
var _a, _b, _c;
|
|
697
|
+
if (!root.fromClause || !this.isBaseTableBinding(root, binding)) {
|
|
698
|
+
return {
|
|
699
|
+
code: "NO_SAFE_UPSTREAM_QUERY",
|
|
700
|
+
reason: "The referenced source is a base table, so there is no upstream query block to move into."
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
if (this.hasOuterJoin(root.fromClause)) {
|
|
704
|
+
return {
|
|
705
|
+
code: "OUTER_JOIN_BOUNDARY",
|
|
706
|
+
reason: "Condition crosses an OUTER JOIN boundary; moving it into JOIN ON may change semantics."
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
const join = binding.isPrimary
|
|
710
|
+
? (_b = (_a = root.fromClause.joins) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : null
|
|
711
|
+
: binding.join;
|
|
712
|
+
if (join === null || join === void 0 ? void 0 : join.lateral) {
|
|
713
|
+
return {
|
|
714
|
+
code: "LATERAL_JOIN_BOUNDARY",
|
|
715
|
+
reason: "Condition crosses a LATERAL JOIN boundary; moving it into JOIN ON may change semantics."
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
if (!join || !this.isInnerJoin(join)) {
|
|
719
|
+
return {
|
|
720
|
+
code: "NO_SAFE_JOIN_ON_TARGET",
|
|
721
|
+
reason: "Base-table conditions are moved only into INNER JOIN ON clauses in the safe-only implementation."
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
if (!(join.condition instanceof JoinOnClause)) {
|
|
725
|
+
return {
|
|
726
|
+
code: "NO_SAFE_JOIN_ON_TARGET",
|
|
727
|
+
reason: "Base-table conditions are moved only into existing JOIN ON clauses, not USING or conditionless joins."
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
return {
|
|
731
|
+
kind: "join_on",
|
|
732
|
+
join,
|
|
733
|
+
scopeId: `join_on:${(_c = join.source.getAliasName()) !== null && _c !== void 0 ? _c : "unknown"}`,
|
|
734
|
+
reason: binding.isPrimary
|
|
735
|
+
? "Condition references the primary source of an INNER JOIN; it is moved into the first JOIN ON clause."
|
|
736
|
+
: "Condition references the joined source of an INNER JOIN; it is moved into that JOIN ON clause."
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
appendJoinOnCondition(join, expression, options) {
|
|
740
|
+
if (!(join.condition instanceof JoinOnClause)) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
join.condition.condition = new BinaryExpression(join.condition.condition, "and", cloneValueComponent(expression, options));
|
|
744
|
+
join.condition.condition = dedupeTopLevelAndConditions(join.condition.condition, options);
|
|
745
|
+
}
|
|
657
746
|
resolveTargetColumnByOutputIndex(root, query, outputIndex, sourceColumn) {
|
|
658
747
|
const output = this.collectSelectOutputs(root, query)[outputIndex];
|
|
659
748
|
if (!output) {
|
|
@@ -662,6 +751,15 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
662
751
|
reason: `UNION branch does not expose column '${sourceColumn.column.name}' at output position ${outputIndex + 1}.`
|
|
663
752
|
};
|
|
664
753
|
}
|
|
754
|
+
else if (identifiersEqual(output.name, sourceColumn.column.name)) {
|
|
755
|
+
const matches = this.collectDirectOutputMatches(root, query, sourceColumn.column.name);
|
|
756
|
+
if (matches.length > 1) {
|
|
757
|
+
return {
|
|
758
|
+
code: "AMBIGUOUS_TARGET_COLUMN",
|
|
759
|
+
reason: `UNION branch exposes multiple '${sourceColumn.column.name}' columns.`
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
}
|
|
665
763
|
if (!(output.value instanceof ColumnReference)) {
|
|
666
764
|
return {
|
|
667
765
|
code: "EXPRESSION_OUTPUT_UNSUPPORTED",
|
|
@@ -704,7 +802,10 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
704
802
|
return { query, targetColumns: [...targetColumns] };
|
|
705
803
|
}
|
|
706
804
|
const upstream = this.resolveUpstreamQuery(contextRoot, binding, targetColumns.map(item => item.targetColumn));
|
|
707
|
-
if ("code" in upstream
|
|
805
|
+
if ("code" in upstream) {
|
|
806
|
+
return { query, targetColumns: [...targetColumns] };
|
|
807
|
+
}
|
|
808
|
+
if (upstream.kind !== "simple") {
|
|
708
809
|
return { query, targetColumns: [...targetColumns] };
|
|
709
810
|
}
|
|
710
811
|
const upstreamColumns = this.resolveTargetColumns(contextRoot, upstream.query, targetColumns.map(item => item.targetColumn));
|
|
@@ -729,9 +830,9 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
729
830
|
};
|
|
730
831
|
}
|
|
731
832
|
const bindings = this.getSourceBindings(query.fromClause);
|
|
732
|
-
const namespace =
|
|
833
|
+
const namespace = column.getNamespace();
|
|
733
834
|
if (namespace) {
|
|
734
|
-
const matches = bindings.filter(binding =>
|
|
835
|
+
const matches = bindings.filter(binding => identifiersEqual(binding.alias, namespace));
|
|
735
836
|
return matches.length === 1
|
|
736
837
|
? null
|
|
737
838
|
: {
|
|
@@ -762,7 +863,7 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
762
863
|
if (sameColumnReference(left, right)) {
|
|
763
864
|
return true;
|
|
764
865
|
}
|
|
765
|
-
if (
|
|
866
|
+
if (!identifiersEqual(left.column.name, right.column.name)) {
|
|
766
867
|
return false;
|
|
767
868
|
}
|
|
768
869
|
const leftSource = this.resolveColumnSourceAlias(query, left);
|
|
@@ -774,12 +875,12 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
774
875
|
return null;
|
|
775
876
|
}
|
|
776
877
|
const bindings = this.getSourceBindings(query.fromClause);
|
|
777
|
-
const namespace =
|
|
878
|
+
const namespace = column.getNamespace();
|
|
778
879
|
if (namespace) {
|
|
779
|
-
const matches = bindings.filter(binding =>
|
|
780
|
-
return matches.length === 1 ?
|
|
880
|
+
const matches = bindings.filter(binding => identifiersEqual(binding.alias, namespace));
|
|
881
|
+
return matches.length === 1 ? matches[0].alias : null;
|
|
781
882
|
}
|
|
782
|
-
return bindings.length === 1 ?
|
|
883
|
+
return bindings.length === 1 ? bindings[0].alias : null;
|
|
783
884
|
}
|
|
784
885
|
rebaseCondition(expression, targetColumns, options) {
|
|
785
886
|
const cloned = cloneValueComponent(expression, options);
|
|
@@ -797,33 +898,45 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
797
898
|
source: fromClause.source,
|
|
798
899
|
alias: (_a = fromClause.source.getAliasName()) !== null && _a !== void 0 ? _a : "",
|
|
799
900
|
join: null,
|
|
901
|
+
joinIndex: -1,
|
|
800
902
|
isPrimary: true
|
|
801
903
|
}];
|
|
802
|
-
for (
|
|
904
|
+
for (let index = 0; index < ((_b = fromClause.joins) !== null && _b !== void 0 ? _b : []).length; index += 1) {
|
|
905
|
+
const join = fromClause.joins[index];
|
|
803
906
|
bindings.push({
|
|
804
907
|
source: join.source,
|
|
805
908
|
alias: (_c = join.source.getAliasName()) !== null && _c !== void 0 ? _c : "",
|
|
806
909
|
join,
|
|
910
|
+
joinIndex: index,
|
|
807
911
|
isPrimary: false
|
|
808
912
|
});
|
|
809
913
|
}
|
|
810
914
|
return bindings;
|
|
811
915
|
}
|
|
916
|
+
isBaseTableBinding(root, binding) {
|
|
917
|
+
const source = binding.source.datasource;
|
|
918
|
+
return source instanceof TableSource && !this.findCte(root, source.table.name);
|
|
919
|
+
}
|
|
920
|
+
isInnerJoin(join) {
|
|
921
|
+
const joinType = join.joinType.value.trim().toLowerCase();
|
|
922
|
+
return joinType === "join" || joinType === "inner join";
|
|
923
|
+
}
|
|
812
924
|
findNullableSideBoundary(fromClause, binding) {
|
|
813
925
|
var _a;
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
return joinType.includes("right") || joinType.includes("full");
|
|
818
|
-
});
|
|
819
|
-
return rightOrFull
|
|
926
|
+
const joins = (_a = fromClause.joins) !== null && _a !== void 0 ? _a : [];
|
|
927
|
+
if (binding.isPrimary) {
|
|
928
|
+
return this.hasLaterJoinThatNullsPriorSources(joins, -1)
|
|
820
929
|
? {
|
|
821
930
|
code: "OUTER_JOIN_NULLABLE_SIDE",
|
|
822
931
|
reason: "Condition crosses OUTER JOIN nullable side; moving it may change semantics."
|
|
823
932
|
}
|
|
824
933
|
: null;
|
|
825
934
|
}
|
|
826
|
-
const
|
|
935
|
+
const join = binding.join;
|
|
936
|
+
if (!join) {
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
const joinType = join.joinType.value.toLowerCase();
|
|
827
940
|
if (joinType.includes("left")) {
|
|
828
941
|
return {
|
|
829
942
|
code: "OUTER_JOIN_NULLABLE_SIDE",
|
|
@@ -836,8 +949,22 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
836
949
|
reason: "Condition crosses FULL JOIN nullable side; moving it may change semantics."
|
|
837
950
|
};
|
|
838
951
|
}
|
|
952
|
+
if (this.hasLaterJoinThatNullsPriorSources(joins, binding.joinIndex)) {
|
|
953
|
+
return {
|
|
954
|
+
code: "OUTER_JOIN_NULLABLE_SIDE",
|
|
955
|
+
reason: "Condition crosses later RIGHT/FULL JOIN nullable side; moving it may change semantics."
|
|
956
|
+
};
|
|
957
|
+
}
|
|
839
958
|
return null;
|
|
840
959
|
}
|
|
960
|
+
hasLaterJoinThatNullsPriorSources(joins, sourceJoinIndex) {
|
|
961
|
+
return joins
|
|
962
|
+
.slice(sourceJoinIndex + 1)
|
|
963
|
+
.some(join => {
|
|
964
|
+
const joinType = join.joinType.value.toLowerCase();
|
|
965
|
+
return joinType.includes("right") || joinType.includes("full");
|
|
966
|
+
});
|
|
967
|
+
}
|
|
841
968
|
hasOuterJoin(fromClause) {
|
|
842
969
|
var _a;
|
|
843
970
|
return ((_a = fromClause.joins) !== null && _a !== void 0 ? _a : []).some(join => {
|
|
@@ -861,12 +988,12 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
861
988
|
if ("code" in branches) {
|
|
862
989
|
return 0;
|
|
863
990
|
}
|
|
864
|
-
return this.
|
|
991
|
+
return this.collectDirectOutputMatches(root, branches[0], columnName).length;
|
|
865
992
|
}
|
|
866
993
|
if (!(target instanceof SimpleSelectQuery)) {
|
|
867
994
|
return 0;
|
|
868
995
|
}
|
|
869
|
-
return this.
|
|
996
|
+
return this.collectDirectOutputMatches(root, target, columnName, true).length;
|
|
870
997
|
}
|
|
871
998
|
collectUnionBranches(query) {
|
|
872
999
|
const branches = [];
|
|
@@ -897,7 +1024,7 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
897
1024
|
resolveUnionOutputIndex(root, firstBranch, outputColumnName) {
|
|
898
1025
|
const matches = [];
|
|
899
1026
|
this.collectSelectOutputs(root, firstBranch).forEach((item, index) => {
|
|
900
|
-
if (
|
|
1027
|
+
if (identifiersEqual(item.name, outputColumnName)) {
|
|
901
1028
|
matches.push(index);
|
|
902
1029
|
}
|
|
903
1030
|
});
|
|
@@ -920,6 +1047,98 @@ export class ParameterConditionPlacementOptimizer {
|
|
|
920
1047
|
const collector = new SelectOutputCollector(null, commonTables.length > 0 ? commonTables : null);
|
|
921
1048
|
return collector.collect(query);
|
|
922
1049
|
}
|
|
1050
|
+
collectDirectOutputMatches(root, query, columnName, includeUnprovenPotential = false) {
|
|
1051
|
+
const collectedMatches = this.collectSelectOutputs(root, query).filter(item => identifiersEqual(item.name, columnName));
|
|
1052
|
+
if (collectedMatches.length > 0) {
|
|
1053
|
+
return this.hasExplicitOutputMatch(query, columnName)
|
|
1054
|
+
? [...collectedMatches, ...this.inferWildcardOutputMatches(root, query, columnName, true)]
|
|
1055
|
+
: collectedMatches;
|
|
1056
|
+
}
|
|
1057
|
+
return this.inferWildcardOutputMatches(root, query, columnName, includeUnprovenPotential);
|
|
1058
|
+
}
|
|
1059
|
+
hasExplicitOutputMatch(query, columnName) {
|
|
1060
|
+
return query.selectClause.items.some(item => {
|
|
1061
|
+
if (item.identifier) {
|
|
1062
|
+
return identifiersEqual(item.identifier.name, columnName);
|
|
1063
|
+
}
|
|
1064
|
+
return item.value instanceof ColumnReference
|
|
1065
|
+
&& item.value.column.name !== "*"
|
|
1066
|
+
&& identifiersEqual(item.value.column.name, columnName);
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
inferWildcardOutputMatches(root, query, columnName, includeUnprovenPotential) {
|
|
1070
|
+
const matches = [];
|
|
1071
|
+
query.selectClause.items.forEach((item, outputIndex) => {
|
|
1072
|
+
if (item.identifier || !(item.value instanceof ColumnReference) || item.value.column.name !== "*") {
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
const targetColumn = this.inferWildcardTargetColumn(root, query, item.value, columnName, includeUnprovenPotential);
|
|
1076
|
+
if (!targetColumn) {
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
if (targetColumn === "ambiguous") {
|
|
1080
|
+
matches.push(this.createInferredOutputColumn(columnName, new ColumnReference(null, columnName), outputIndex), this.createInferredOutputColumn(columnName, new ColumnReference(null, columnName), outputIndex));
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
matches.push(this.createInferredOutputColumn(columnName, targetColumn, outputIndex));
|
|
1084
|
+
});
|
|
1085
|
+
return matches;
|
|
1086
|
+
}
|
|
1087
|
+
inferWildcardTargetColumn(root, query, wildcard, columnName, includeUnprovenPotential) {
|
|
1088
|
+
if (!query.fromClause) {
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
const bindings = this.getSourceBindings(query.fromClause);
|
|
1092
|
+
if (wildcard.namespaces === null) {
|
|
1093
|
+
if (bindings.length !== 1) {
|
|
1094
|
+
return "ambiguous";
|
|
1095
|
+
}
|
|
1096
|
+
return this.resolveWildcardColumnFromBinding(root, query, bindings[0], columnName, includeUnprovenPotential);
|
|
1097
|
+
}
|
|
1098
|
+
const namespace = wildcard.getNamespace();
|
|
1099
|
+
const matches = bindings.filter(binding => identifiersEqual(binding.alias, namespace));
|
|
1100
|
+
if (matches.length === 0) {
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
return matches.length === 1
|
|
1104
|
+
? this.resolveWildcardColumnFromBinding(root, query, matches[0], columnName, includeUnprovenPotential)
|
|
1105
|
+
: "ambiguous";
|
|
1106
|
+
}
|
|
1107
|
+
resolveWildcardColumnFromBinding(root, query, binding, columnName, includeUnprovenPotential) {
|
|
1108
|
+
const matches = this.collectSourceOutputMatches(root, query, binding.source, columnName);
|
|
1109
|
+
if (matches.length > 1) {
|
|
1110
|
+
return "ambiguous";
|
|
1111
|
+
}
|
|
1112
|
+
if (matches.length === 1) {
|
|
1113
|
+
const value = matches[0].value;
|
|
1114
|
+
return value instanceof ColumnReference ? value : null;
|
|
1115
|
+
}
|
|
1116
|
+
return includeUnprovenPotential && binding.source.datasource instanceof TableSource
|
|
1117
|
+
? this.createColumnForBinding(binding, columnName)
|
|
1118
|
+
: null;
|
|
1119
|
+
}
|
|
1120
|
+
collectSourceOutputMatches(root, query, source, columnName) {
|
|
1121
|
+
var _a, _b, _c, _d;
|
|
1122
|
+
const commonTables = [
|
|
1123
|
+
...((_b = (_a = query.withClause) === null || _a === void 0 ? void 0 : _a.tables) !== null && _b !== void 0 ? _b : []),
|
|
1124
|
+
...((_d = (_c = root.withClause) === null || _c === void 0 ? void 0 : _c.tables) !== null && _d !== void 0 ? _d : [])
|
|
1125
|
+
];
|
|
1126
|
+
const collector = new SelectOutputCollector(null, commonTables.length > 0 ? commonTables : null);
|
|
1127
|
+
return collector.collect(source).filter(item => identifiersEqual(item.name, columnName));
|
|
1128
|
+
}
|
|
1129
|
+
createColumnForBinding(binding, columnName) {
|
|
1130
|
+
return new ColumnReference(binding.alias ? [binding.alias] : null, columnName);
|
|
1131
|
+
}
|
|
1132
|
+
createInferredOutputColumn(name, value, outputIndex) {
|
|
1133
|
+
return {
|
|
1134
|
+
name,
|
|
1135
|
+
value,
|
|
1136
|
+
outputIndex,
|
|
1137
|
+
sourceAlias: value.getNamespace() || null,
|
|
1138
|
+
sourceName: null,
|
|
1139
|
+
sourceColumnName: name
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
923
1142
|
resolveSourceQueryForColumns(root, source) {
|
|
924
1143
|
var _a;
|
|
925
1144
|
if (source.datasource instanceof SubQuerySource) {
|