@quereus/quereus 2.8.0 → 2.9.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/src/emit/ast-stringify.d.ts.map +1 -1
- package/dist/src/emit/ast-stringify.js +0 -3
- package/dist/src/emit/ast-stringify.js.map +1 -1
- package/dist/src/parser/ast.d.ts +3 -2
- package/dist/src/parser/ast.d.ts.map +1 -1
- package/dist/src/parser/parser.d.ts.map +1 -1
- package/dist/src/parser/parser.js +13 -4
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/planner/building/foreign-key-builder.d.ts.map +1 -1
- package/dist/src/planner/building/foreign-key-builder.js +3 -2
- package/dist/src/planner/building/foreign-key-builder.js.map +1 -1
- package/dist/src/planner/building/select.js +14 -2
- package/dist/src/planner/building/select.js.map +1 -1
- package/dist/src/planner/building/update.d.ts.map +1 -1
- package/dist/src/planner/building/update.js +10 -6
- package/dist/src/planner/building/update.js.map +1 -1
- package/dist/src/planner/framework/characteristics.d.ts +3 -1
- package/dist/src/planner/framework/characteristics.d.ts.map +1 -1
- package/dist/src/planner/framework/characteristics.js +7 -0
- package/dist/src/planner/framework/characteristics.js.map +1 -1
- package/dist/src/planner/framework/physical-utils.d.ts +22 -1
- package/dist/src/planner/framework/physical-utils.d.ts.map +1 -1
- package/dist/src/planner/framework/physical-utils.js +47 -0
- package/dist/src/planner/framework/physical-utils.js.map +1 -1
- package/dist/src/planner/nodes/alias-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/alias-node.js +2 -0
- package/dist/src/planner/nodes/alias-node.js.map +1 -1
- package/dist/src/planner/nodes/asof-scan-node.d.ts +137 -0
- package/dist/src/planner/nodes/asof-scan-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/asof-scan-node.js +223 -0
- package/dist/src/planner/nodes/asof-scan-node.js.map +1 -0
- package/dist/src/planner/nodes/constraint-check-node.d.ts +3 -0
- package/dist/src/planner/nodes/constraint-check-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/constraint-check-node.js.map +1 -1
- package/dist/src/planner/nodes/distinct-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/distinct-node.js +7 -0
- package/dist/src/planner/nodes/distinct-node.js.map +1 -1
- package/dist/src/planner/nodes/filter.d.ts.map +1 -1
- package/dist/src/planner/nodes/filter.js +2 -0
- package/dist/src/planner/nodes/filter.js.map +1 -1
- package/dist/src/planner/nodes/function.d.ts +11 -1
- package/dist/src/planner/nodes/function.d.ts.map +1 -1
- package/dist/src/planner/nodes/function.js +94 -1
- package/dist/src/planner/nodes/function.js.map +1 -1
- package/dist/src/planner/nodes/hash-aggregate.d.ts.map +1 -1
- package/dist/src/planner/nodes/hash-aggregate.js +2 -0
- package/dist/src/planner/nodes/hash-aggregate.js.map +1 -1
- package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/join-node.js +10 -2
- package/dist/src/planner/nodes/join-node.js.map +1 -1
- package/dist/src/planner/nodes/join-utils.d.ts +19 -1
- package/dist/src/planner/nodes/join-utils.d.ts.map +1 -1
- package/dist/src/planner/nodes/join-utils.js +46 -0
- package/dist/src/planner/nodes/join-utils.js.map +1 -1
- package/dist/src/planner/nodes/limit-offset.d.ts.map +1 -1
- package/dist/src/planner/nodes/limit-offset.js +2 -0
- package/dist/src/planner/nodes/limit-offset.js.map +1 -1
- package/dist/src/planner/nodes/merge-join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/merge-join-node.js +4 -1
- package/dist/src/planner/nodes/merge-join-node.js.map +1 -1
- package/dist/src/planner/nodes/ordinal-slice-node.d.ts +50 -0
- package/dist/src/planner/nodes/ordinal-slice-node.d.ts.map +1 -0
- package/dist/src/planner/nodes/ordinal-slice-node.js +127 -0
- package/dist/src/planner/nodes/ordinal-slice-node.js.map +1 -0
- package/dist/src/planner/nodes/plan-node-type.d.ts +2 -0
- package/dist/src/planner/nodes/plan-node-type.d.ts.map +1 -1
- package/dist/src/planner/nodes/plan-node-type.js +2 -0
- package/dist/src/planner/nodes/plan-node-type.js.map +1 -1
- package/dist/src/planner/nodes/plan-node.d.ts +130 -1
- package/dist/src/planner/nodes/plan-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/plan-node.js +49 -0
- package/dist/src/planner/nodes/plan-node.js.map +1 -1
- package/dist/src/planner/nodes/project-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/project-node.js +6 -1
- package/dist/src/planner/nodes/project-node.js.map +1 -1
- package/dist/src/planner/nodes/reference.d.ts +4 -1
- package/dist/src/planner/nodes/reference.d.ts.map +1 -1
- package/dist/src/planner/nodes/reference.js +16 -0
- package/dist/src/planner/nodes/reference.js.map +1 -1
- package/dist/src/planner/nodes/scalar.d.ts +7 -1
- package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
- package/dist/src/planner/nodes/scalar.js +100 -1
- package/dist/src/planner/nodes/scalar.js.map +1 -1
- package/dist/src/planner/nodes/set-operation-node.d.ts +2 -1
- package/dist/src/planner/nodes/set-operation-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/set-operation-node.js +9 -0
- package/dist/src/planner/nodes/set-operation-node.js.map +1 -1
- package/dist/src/planner/nodes/sort.d.ts.map +1 -1
- package/dist/src/planner/nodes/sort.js +20 -0
- package/dist/src/planner/nodes/sort.js.map +1 -1
- package/dist/src/planner/nodes/stream-aggregate.d.ts.map +1 -1
- package/dist/src/planner/nodes/stream-aggregate.js +2 -0
- package/dist/src/planner/nodes/stream-aggregate.js.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.d.ts +37 -2
- package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.js +87 -10
- package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
- package/dist/src/planner/nodes/update-node.d.ts +1 -3
- package/dist/src/planner/nodes/update-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/update-node.js +3 -9
- package/dist/src/planner/nodes/update-node.js.map +1 -1
- package/dist/src/planner/nodes/window-node.d.ts +61 -2
- package/dist/src/planner/nodes/window-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/window-node.js +64 -3
- package/dist/src/planner/nodes/window-node.js.map +1 -1
- package/dist/src/planner/optimizer-tuning.d.ts +9 -0
- package/dist/src/planner/optimizer-tuning.d.ts.map +1 -1
- package/dist/src/planner/optimizer-tuning.js +3 -0
- package/dist/src/planner/optimizer-tuning.js.map +1 -1
- package/dist/src/planner/optimizer.d.ts.map +1 -1
- package/dist/src/planner/optimizer.js +91 -0
- package/dist/src/planner/optimizer.js.map +1 -1
- package/dist/src/planner/rules/access/rule-asof-strategy-select.d.ts +30 -0
- package/dist/src/planner/rules/access/rule-asof-strategy-select.d.ts.map +1 -0
- package/dist/src/planner/rules/access/rule-asof-strategy-select.js +112 -0
- package/dist/src/planner/rules/access/rule-asof-strategy-select.js.map +1 -0
- package/dist/src/planner/rules/access/rule-monotonic-limit-pushdown.d.ts +33 -0
- package/dist/src/planner/rules/access/rule-monotonic-limit-pushdown.d.ts.map +1 -0
- package/dist/src/planner/rules/access/rule-monotonic-limit-pushdown.js +162 -0
- package/dist/src/planner/rules/access/rule-monotonic-limit-pushdown.js.map +1 -0
- package/dist/src/planner/rules/access/rule-monotonic-range-access.d.ts +29 -0
- package/dist/src/planner/rules/access/rule-monotonic-range-access.d.ts.map +1 -0
- package/dist/src/planner/rules/access/rule-monotonic-range-access.js +175 -0
- package/dist/src/planner/rules/access/rule-monotonic-range-access.js.map +1 -0
- package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.js +31 -10
- package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
- package/dist/src/planner/rules/join/equi-pair-extractor.d.ts +61 -0
- package/dist/src/planner/rules/join/equi-pair-extractor.d.ts.map +1 -0
- package/dist/src/planner/rules/join/equi-pair-extractor.js +155 -0
- package/dist/src/planner/rules/join/equi-pair-extractor.js.map +1 -0
- package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-join-physical-selection.js +2 -122
- package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
- package/dist/src/planner/rules/join/rule-lateral-top1-asof.d.ts +21 -0
- package/dist/src/planner/rules/join/rule-lateral-top1-asof.d.ts.map +1 -0
- package/dist/src/planner/rules/join/rule-lateral-top1-asof.js +405 -0
- package/dist/src/planner/rules/join/rule-lateral-top1-asof.js.map +1 -0
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts +31 -0
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts.map +1 -0
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.js +113 -0
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.js.map +1 -0
- package/dist/src/planner/rules/window/rule-monotonic-window.d.ts +47 -0
- package/dist/src/planner/rules/window/rule-monotonic-window.d.ts.map +1 -0
- package/dist/src/planner/rules/window/rule-monotonic-window.js +341 -0
- package/dist/src/planner/rules/window/rule-monotonic-window.js.map +1 -0
- package/dist/src/runtime/context-helpers.d.ts +9 -0
- package/dist/src/runtime/context-helpers.d.ts.map +1 -1
- package/dist/src/runtime/context-helpers.js +5 -0
- package/dist/src/runtime/context-helpers.js.map +1 -1
- package/dist/src/runtime/emit/asof-scan.d.ts +10 -0
- package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -0
- package/dist/src/runtime/emit/asof-scan.js +467 -0
- package/dist/src/runtime/emit/asof-scan.js.map +1 -0
- package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
- package/dist/src/runtime/emit/constraint-check.js +20 -0
- package/dist/src/runtime/emit/constraint-check.js.map +1 -1
- package/dist/src/runtime/emit/ordinal-slice.d.ts +13 -0
- package/dist/src/runtime/emit/ordinal-slice.d.ts.map +1 -0
- package/dist/src/runtime/emit/ordinal-slice.js +89 -0
- package/dist/src/runtime/emit/ordinal-slice.js.map +1 -0
- package/dist/src/runtime/emit/returning.d.ts.map +1 -1
- package/dist/src/runtime/emit/returning.js +9 -4
- package/dist/src/runtime/emit/returning.js.map +1 -1
- package/dist/src/runtime/emit/scan.d.ts +19 -3
- package/dist/src/runtime/emit/scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/scan.js +12 -8
- package/dist/src/runtime/emit/scan.js.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.d.ts.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.js +91 -14
- package/dist/src/runtime/emit/schema-declarative.js.map +1 -1
- package/dist/src/runtime/emit/window.d.ts.map +1 -1
- package/dist/src/runtime/emit/window.js +732 -37
- package/dist/src/runtime/emit/window.js.map +1 -1
- package/dist/src/runtime/foreign-key-actions.js +5 -4
- package/dist/src/runtime/foreign-key-actions.js.map +1 -1
- package/dist/src/runtime/register.d.ts.map +1 -1
- package/dist/src/runtime/register.js +4 -0
- package/dist/src/runtime/register.js.map +1 -1
- package/dist/src/schema/catalog.d.ts +10 -0
- package/dist/src/schema/catalog.d.ts.map +1 -1
- package/dist/src/schema/catalog.js +29 -6
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/function.d.ts +24 -0
- package/dist/src/schema/function.d.ts.map +1 -1
- package/dist/src/schema/function.js.map +1 -1
- package/dist/src/schema/manager.d.ts +10 -0
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +10 -0
- package/dist/src/schema/manager.js.map +1 -1
- package/dist/src/schema/schema-differ.d.ts +18 -1
- package/dist/src/schema/schema-differ.d.ts.map +1 -1
- package/dist/src/schema/schema-differ.js +307 -42
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/types/logical-type.d.ts +11 -0
- package/dist/src/types/logical-type.d.ts.map +1 -1
- package/dist/src/types/logical-type.js.map +1 -1
- package/dist/src/util/ast-literal.d.ts +11 -0
- package/dist/src/util/ast-literal.d.ts.map +1 -0
- package/dist/src/util/ast-literal.js +26 -0
- package/dist/src/util/ast-literal.js.map +1 -0
- package/dist/src/vtab/best-access-plan.d.ts +41 -0
- package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
- package/dist/src/vtab/best-access-plan.js +29 -0
- package/dist/src/vtab/best-access-plan.js.map +1 -1
- package/dist/src/vtab/filter-info.d.ts +14 -0
- package/dist/src/vtab/filter-info.d.ts.map +1 -1
- package/dist/src/vtab/memory/module.d.ts +39 -1
- package/dist/src/vtab/memory/module.d.ts.map +1 -1
- package/dist/src/vtab/memory/module.js +206 -44
- package/dist/src/vtab/memory/module.js.map +1 -1
- package/dist/src/vtab/module.d.ts +24 -0
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: Recognize lateral-top-1 over asof predicate, replace with AsofScanNode.
|
|
3
|
+
*
|
|
4
|
+
* Pattern (the rule peels through these in any nesting order, ignoring AliasNode):
|
|
5
|
+
*
|
|
6
|
+
* JoinNode (joinType ∈ {inner, left, cross} with `on true` or no condition)
|
|
7
|
+
* left: Left
|
|
8
|
+
* right: AliasNode? | ProjectNode | LimitOffsetNode(LIMIT 1, no OFFSET) | SortNode
|
|
9
|
+
* ...peeled in any nesting order...
|
|
10
|
+
* └─ FilterNode (ANDed: q.K op left.K AND q.P_i = left.P_i ...)
|
|
11
|
+
* └─ <physical right> with monotonicOn(K) and accessCapabilities.asofRight
|
|
12
|
+
*
|
|
13
|
+
* The right side must be correlated against the left (uses outer columns). On
|
|
14
|
+
* a successful match we emit an AsofScanNode preserving the JoinNode's output
|
|
15
|
+
* attribute IDs; otherwise we return null and the existing physical-join
|
|
16
|
+
* selection takes over.
|
|
17
|
+
*/
|
|
18
|
+
import { createLogger } from '../../../common/logger.js';
|
|
19
|
+
import { isRelationalNode } from '../../nodes/plan-node.js';
|
|
20
|
+
import { JoinNode } from '../../nodes/join-node.js';
|
|
21
|
+
import { AsofScanNode } from '../../nodes/asof-scan-node.js';
|
|
22
|
+
import { ProjectNode } from '../../nodes/project-node.js';
|
|
23
|
+
import { LimitOffsetNode } from '../../nodes/limit-offset.js';
|
|
24
|
+
import { SortNode } from '../../nodes/sort.js';
|
|
25
|
+
import { FilterNode } from '../../nodes/filter.js';
|
|
26
|
+
import { AliasNode } from '../../nodes/alias-node.js';
|
|
27
|
+
import { BinaryOpNode, LiteralNode } from '../../nodes/scalar.js';
|
|
28
|
+
import { ColumnReferenceNode, TableReferenceNode } from '../../nodes/reference.js';
|
|
29
|
+
import { RetrieveNode } from '../../nodes/retrieve-node.js';
|
|
30
|
+
import { isCorrelatedSubquery } from '../../cache/correlation-detector.js';
|
|
31
|
+
const log = createLogger('optimizer:rule:lateral-top1-asof');
|
|
32
|
+
/**
|
|
33
|
+
* Peel the right subtree of the lateral, looking for the canonical
|
|
34
|
+
* (Project | LimitOffset | Sort | Alias)* → Filter chain.
|
|
35
|
+
*/
|
|
36
|
+
function peelLateral(right) {
|
|
37
|
+
let project;
|
|
38
|
+
let limit;
|
|
39
|
+
let sort;
|
|
40
|
+
let cursor = right;
|
|
41
|
+
let safety = 16;
|
|
42
|
+
while (safety-- > 0) {
|
|
43
|
+
if (cursor instanceof AliasNode) {
|
|
44
|
+
cursor = cursor.source;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (cursor instanceof ProjectNode) {
|
|
48
|
+
if (project)
|
|
49
|
+
return null; // multiple Projects not handled
|
|
50
|
+
project = cursor;
|
|
51
|
+
cursor = cursor.source;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (cursor instanceof LimitOffsetNode) {
|
|
55
|
+
if (limit)
|
|
56
|
+
return null;
|
|
57
|
+
limit = cursor;
|
|
58
|
+
cursor = cursor.source;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (cursor instanceof SortNode) {
|
|
62
|
+
if (sort)
|
|
63
|
+
return null;
|
|
64
|
+
sort = cursor;
|
|
65
|
+
cursor = cursor.source;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
if (!limit || !sort || !(cursor instanceof FilterNode))
|
|
71
|
+
return null;
|
|
72
|
+
return { filter: cursor, limit, sort, project };
|
|
73
|
+
}
|
|
74
|
+
/** Verify that the limit is the literal integer 1 and there's no offset. */
|
|
75
|
+
function isLimitOne(limit) {
|
|
76
|
+
if (limit.offset && !isLiteralZeroOrUndefined(limit.offset))
|
|
77
|
+
return false;
|
|
78
|
+
if (!limit.limit)
|
|
79
|
+
return false;
|
|
80
|
+
if (!(limit.limit instanceof LiteralNode))
|
|
81
|
+
return false;
|
|
82
|
+
const v = limit.limit.expression.value;
|
|
83
|
+
if (typeof v === 'number')
|
|
84
|
+
return v === 1;
|
|
85
|
+
if (typeof v === 'bigint')
|
|
86
|
+
return v === 1n;
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
/** A null-or-zero literal for OFFSET. */
|
|
90
|
+
function isLiteralZeroOrUndefined(node) {
|
|
91
|
+
if (!(node instanceof LiteralNode))
|
|
92
|
+
return false;
|
|
93
|
+
const v = node.expression.value;
|
|
94
|
+
if (v === null || v === undefined)
|
|
95
|
+
return true;
|
|
96
|
+
if (typeof v === 'number')
|
|
97
|
+
return v === 0;
|
|
98
|
+
if (typeof v === 'bigint')
|
|
99
|
+
return v === 0n;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Verify the SortNode has a single key that is a trivial column reference and
|
|
104
|
+
* a definite direction. Returns the attribute id and direction, or null.
|
|
105
|
+
*/
|
|
106
|
+
function extractSortAttrId(sort) {
|
|
107
|
+
if (sort.sortKeys.length !== 1)
|
|
108
|
+
return null;
|
|
109
|
+
const key = sort.sortKeys[0];
|
|
110
|
+
if (key.direction !== 'desc' && key.direction !== 'asc')
|
|
111
|
+
return null;
|
|
112
|
+
if (!(key.expression instanceof ColumnReferenceNode))
|
|
113
|
+
return null;
|
|
114
|
+
return { attrId: key.expression.attributeId, direction: key.direction };
|
|
115
|
+
}
|
|
116
|
+
/** Walk an AND tree, returning each leaf conjunct. */
|
|
117
|
+
function flattenAnd(node) {
|
|
118
|
+
const result = [];
|
|
119
|
+
const stack = [node];
|
|
120
|
+
while (stack.length) {
|
|
121
|
+
const cur = stack.pop();
|
|
122
|
+
if (cur instanceof BinaryOpNode && cur.expression.operator === 'AND') {
|
|
123
|
+
stack.push(cur.left, cur.right);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
result.push(cur);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Classify each conjunct of the lateral's Filter.
|
|
133
|
+
*
|
|
134
|
+
* `rightAttrIds` is the set of attribute IDs defined within the lateral's
|
|
135
|
+
* inner subtree (anything below the Filter). The asof inequality must
|
|
136
|
+
* resolve to (right K op left K); equalities must resolve to (right P = left P).
|
|
137
|
+
*
|
|
138
|
+
* Returns null when any conjunct does not fit the asof shape.
|
|
139
|
+
*/
|
|
140
|
+
function classifyPredicates(conjuncts, rightAttrIds) {
|
|
141
|
+
let asof;
|
|
142
|
+
const partition = [];
|
|
143
|
+
for (const c of conjuncts) {
|
|
144
|
+
if (!(c instanceof BinaryOpNode))
|
|
145
|
+
return null;
|
|
146
|
+
if (!(c.left instanceof ColumnReferenceNode) || !(c.right instanceof ColumnReferenceNode))
|
|
147
|
+
return null;
|
|
148
|
+
const op = c.expression.operator;
|
|
149
|
+
const lAttrId = c.left.attributeId;
|
|
150
|
+
const rAttrId = c.right.attributeId;
|
|
151
|
+
const lFromRight = rightAttrIds.has(lAttrId);
|
|
152
|
+
const rFromRight = rightAttrIds.has(rAttrId);
|
|
153
|
+
// Both sides referencing the same side are not valid asof shape.
|
|
154
|
+
if (lFromRight === rFromRight)
|
|
155
|
+
return null;
|
|
156
|
+
// Canonicalize as (right side, left side).
|
|
157
|
+
const rightAttrId = lFromRight ? lAttrId : rAttrId;
|
|
158
|
+
const leftAttrId = lFromRight ? rAttrId : lAttrId;
|
|
159
|
+
if (op === '=') {
|
|
160
|
+
partition.push({ leftAttrId, rightAttrId });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// Asof inequality. Two directions are supported:
|
|
164
|
+
// 'desc' — right ≤ left (latest right ≤ left); strict variant: right < left.
|
|
165
|
+
// 'asc' — right ≥ left (earliest right ≥ left); strict variant: right > left.
|
|
166
|
+
// All operator forms canonicalize to (right op left) before mapping:
|
|
167
|
+
// - q.K <= t.K → desc, strict=false - q.K >= t.K → asc, strict=false
|
|
168
|
+
// - q.K < t.K → desc, strict=true - q.K > t.K → asc, strict=true
|
|
169
|
+
// - t.K >= q.K → desc, strict=false - t.K <= q.K → asc, strict=false
|
|
170
|
+
// - t.K > q.K → desc, strict=true - t.K < q.K → asc, strict=true
|
|
171
|
+
let strict;
|
|
172
|
+
let direction;
|
|
173
|
+
if (lFromRight) {
|
|
174
|
+
// op is between (right.col, left.col)
|
|
175
|
+
if (op === '<=') {
|
|
176
|
+
strict = false;
|
|
177
|
+
direction = 'desc';
|
|
178
|
+
}
|
|
179
|
+
else if (op === '<') {
|
|
180
|
+
strict = true;
|
|
181
|
+
direction = 'desc';
|
|
182
|
+
}
|
|
183
|
+
else if (op === '>=') {
|
|
184
|
+
strict = false;
|
|
185
|
+
direction = 'asc';
|
|
186
|
+
}
|
|
187
|
+
else if (op === '>') {
|
|
188
|
+
strict = true;
|
|
189
|
+
direction = 'asc';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// op is between (left.col, right.col); flip
|
|
194
|
+
if (op === '>=') {
|
|
195
|
+
strict = false;
|
|
196
|
+
direction = 'desc';
|
|
197
|
+
}
|
|
198
|
+
else if (op === '>') {
|
|
199
|
+
strict = true;
|
|
200
|
+
direction = 'desc';
|
|
201
|
+
}
|
|
202
|
+
else if (op === '<=') {
|
|
203
|
+
strict = false;
|
|
204
|
+
direction = 'asc';
|
|
205
|
+
}
|
|
206
|
+
else if (op === '<') {
|
|
207
|
+
strict = true;
|
|
208
|
+
direction = 'asc';
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (strict === undefined || direction === undefined)
|
|
212
|
+
return null;
|
|
213
|
+
if (asof)
|
|
214
|
+
return null; // multiple asof inequalities — bail
|
|
215
|
+
asof = { rightAttrId, leftAttrId, strict, direction };
|
|
216
|
+
}
|
|
217
|
+
if (!asof)
|
|
218
|
+
return null;
|
|
219
|
+
return { asof, partition };
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Find the table reference at the bottom of a structural pipeline (Filter →
|
|
223
|
+
* AliasNode → ProjectNode → ... → Retrieve → TableReference).
|
|
224
|
+
*
|
|
225
|
+
* Returns null when the chain branches or terminates before reaching a leaf
|
|
226
|
+
* that's a direct TableReference.
|
|
227
|
+
*/
|
|
228
|
+
function findTableReference(node) {
|
|
229
|
+
let cur = node;
|
|
230
|
+
let safety = 64;
|
|
231
|
+
while (safety-- > 0) {
|
|
232
|
+
if (cur instanceof TableReferenceNode)
|
|
233
|
+
return cur;
|
|
234
|
+
if (cur instanceof RetrieveNode) {
|
|
235
|
+
cur = cur.tableRef;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const relChildren = cur.getChildren().filter(isRelationalNode);
|
|
239
|
+
if (relChildren.length !== 1)
|
|
240
|
+
return null;
|
|
241
|
+
cur = relChildren[0];
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Probe the table's vtab module to see if its best-access plan would advertise
|
|
247
|
+
* `supportsAsofRight` and `monotonicOn` on `column` for an ordered, unfiltered
|
|
248
|
+
* scan. If the module lacks `getBestAccessPlan`, returns false.
|
|
249
|
+
*/
|
|
250
|
+
function tableAdvertisesAsof(context, tableRef, column) {
|
|
251
|
+
const vtabModule = tableRef.vtabModule;
|
|
252
|
+
if (!vtabModule.getBestAccessPlan)
|
|
253
|
+
return false;
|
|
254
|
+
const tableSchema = tableRef.tableSchema;
|
|
255
|
+
const columns = tableSchema.columns.map((col, index) => ({
|
|
256
|
+
index,
|
|
257
|
+
name: col.name,
|
|
258
|
+
type: col.logicalType,
|
|
259
|
+
isPrimaryKey: col.primaryKey || false,
|
|
260
|
+
isUnique: col.primaryKey || false,
|
|
261
|
+
}));
|
|
262
|
+
const request = {
|
|
263
|
+
columns,
|
|
264
|
+
filters: [],
|
|
265
|
+
requiredOrdering: [{ columnIndex: column, desc: false }],
|
|
266
|
+
estimatedRows: tableRef.estimatedRows ?? undefined,
|
|
267
|
+
};
|
|
268
|
+
try {
|
|
269
|
+
const plan = vtabModule.getBestAccessPlan(context.db, tableSchema, request);
|
|
270
|
+
if (!plan.supportsAsofRight)
|
|
271
|
+
return false;
|
|
272
|
+
if (!plan.monotonicOn)
|
|
273
|
+
return false;
|
|
274
|
+
if (plan.monotonicOn.columnIndex !== column)
|
|
275
|
+
return false;
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function isTriviallyTrue(condition) {
|
|
283
|
+
if (condition instanceof LiteralNode) {
|
|
284
|
+
const v = condition.expression.value;
|
|
285
|
+
return v === true || v === 1 || v === 1n;
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Build right output column indices and preserve the projection's attribute IDs
|
|
291
|
+
* (so the parent of the JoinNode keeps seeing the same attribute IDs after the
|
|
292
|
+
* rewrite).
|
|
293
|
+
*
|
|
294
|
+
* Returns null when any projection isn't a trivial column reference into the
|
|
295
|
+
* Filter source.
|
|
296
|
+
*/
|
|
297
|
+
function resolveProjectedRightAttrs(project, joinAttrs, leftAttrCount, filterSourceAttrs) {
|
|
298
|
+
const filterIdToIndex = new Map(filterSourceAttrs.map((a, i) => [a.id, i]));
|
|
299
|
+
const rightOutputAttrs = joinAttrs.slice(leftAttrCount);
|
|
300
|
+
if (!project) {
|
|
301
|
+
// No projection — emit the filter source's columns directly. Attribute
|
|
302
|
+
// IDs match the filter source already (Project/Alias would have wrapped
|
|
303
|
+
// otherwise).
|
|
304
|
+
return {
|
|
305
|
+
columnIndices: filterSourceAttrs.map((_, i) => i),
|
|
306
|
+
attrs: rightOutputAttrs.slice(),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
// With a projection: each projection node must be a trivial column ref into
|
|
310
|
+
// the filter source. Map its source attribute id → column index.
|
|
311
|
+
const projections = project.projections;
|
|
312
|
+
if (projections.length !== rightOutputAttrs.length)
|
|
313
|
+
return null;
|
|
314
|
+
const columnIndices = [];
|
|
315
|
+
for (let i = 0; i < projections.length; i++) {
|
|
316
|
+
const projNode = projections[i].node;
|
|
317
|
+
if (!(projNode instanceof ColumnReferenceNode))
|
|
318
|
+
return null;
|
|
319
|
+
const idx = filterIdToIndex.get(projNode.attributeId);
|
|
320
|
+
if (idx === undefined)
|
|
321
|
+
return null;
|
|
322
|
+
columnIndices.push(idx);
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
columnIndices,
|
|
326
|
+
attrs: rightOutputAttrs.slice(),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
export function ruleLateralTop1Asof(node, context) {
|
|
330
|
+
if (!(node instanceof JoinNode))
|
|
331
|
+
return null;
|
|
332
|
+
const joinType = node.joinType;
|
|
333
|
+
// LATERAL is meaningful for inner / left / cross with `ON TRUE` (or no condition).
|
|
334
|
+
if (joinType !== 'inner' && joinType !== 'left' && joinType !== 'cross')
|
|
335
|
+
return null;
|
|
336
|
+
// JoinNode condition must be absent or trivially true (LATERAL ON TRUE).
|
|
337
|
+
if (node.condition && !isTriviallyTrue(node.condition))
|
|
338
|
+
return null;
|
|
339
|
+
// Right subtree must be correlated against the outer (left) attributes.
|
|
340
|
+
if (!isCorrelatedSubquery(node.right))
|
|
341
|
+
return null;
|
|
342
|
+
const peeled = peelLateral(node.right);
|
|
343
|
+
if (!peeled)
|
|
344
|
+
return null;
|
|
345
|
+
if (!isLimitOne(peeled.limit))
|
|
346
|
+
return null;
|
|
347
|
+
const sortInfo = extractSortAttrId(peeled.sort);
|
|
348
|
+
if (sortInfo === null)
|
|
349
|
+
return null;
|
|
350
|
+
// The Filter's source defines the right attribute set we'll classify against.
|
|
351
|
+
const filterSourceAttrs = peeled.filter.source.getAttributes();
|
|
352
|
+
const rightAttrIds = new Set(filterSourceAttrs.map(a => a.id));
|
|
353
|
+
const conjuncts = flattenAnd(peeled.filter.predicate);
|
|
354
|
+
const classified = classifyPredicates(conjuncts, rightAttrIds);
|
|
355
|
+
if (!classified)
|
|
356
|
+
return null;
|
|
357
|
+
// Sort key must match the asof match attribute on the right.
|
|
358
|
+
if (classified.asof.rightAttrId !== sortInfo.attrId)
|
|
359
|
+
return null;
|
|
360
|
+
// Sort direction must match the asof direction (desc → latest-le, asc → earliest-ge).
|
|
361
|
+
if (sortInfo.direction !== classified.asof.direction)
|
|
362
|
+
return null;
|
|
363
|
+
// Locate the underlying table reference at the bottom of the lateral.
|
|
364
|
+
const tableRef = findTableReference(peeled.filter.source);
|
|
365
|
+
if (!tableRef) {
|
|
366
|
+
log('Right subtree does not bottom out in a TableReference');
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
// Translate the asof match attribute id to a table column index.
|
|
370
|
+
const tableAttrs = tableRef.getAttributes();
|
|
371
|
+
const matchColumnIdx = tableAttrs.findIndex(a => a.id === classified.asof.rightAttrId);
|
|
372
|
+
if (matchColumnIdx === -1) {
|
|
373
|
+
log('Asof match attr %d is not a column of the underlying table', classified.asof.rightAttrId);
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
// Probe the vtab module for asofRight + monotonicOn(K).
|
|
377
|
+
if (!tableAdvertisesAsof(context, tableRef, matchColumnIdx)) {
|
|
378
|
+
log('Vtab module does not advertise monotonicOn(col=%d) + asofRight', matchColumnIdx);
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
// Left must be monotonic on the match attribute (its cursor cannot regress
|
|
382
|
+
// per partition). Without this, the streaming scan would produce wrong rows
|
|
383
|
+
// for left rows whose match value decreases. We require global
|
|
384
|
+
// monotonicOn(left.matchAttr) — stronger than "monotonic within partition"
|
|
385
|
+
// but the only check we can make uniformly. The user can wrap the left in
|
|
386
|
+
// `ORDER BY matchAttr` to satisfy this.
|
|
387
|
+
const leftMonotonic = node.left.physical?.monotonicOn?.find(m => m.attrId === classified.asof.leftAttrId);
|
|
388
|
+
if (!leftMonotonic) {
|
|
389
|
+
log('Left input is not monotonicOn(left.matchAttr=%d); skipping asof rewrite', classified.asof.leftAttrId);
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
// Resolve the right output projection (preserving the original Project's
|
|
393
|
+
// attribute IDs so the parent of the join keeps the same IDs after rewrite).
|
|
394
|
+
const joinAttrs = node.getAttributes();
|
|
395
|
+
const leftAttrCount = node.left.getAttributes().length;
|
|
396
|
+
const projection = resolveProjectedRightAttrs(peeled.project, joinAttrs, leftAttrCount, filterSourceAttrs);
|
|
397
|
+
if (!projection) {
|
|
398
|
+
log('Lateral projection is non-trivial; skipping');
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
const asof = new AsofScanNode(node.scope, node.left, peeled.filter.source, { leftAttrId: classified.asof.leftAttrId, rightAttrId: classified.asof.rightAttrId }, classified.partition, classified.asof.strict, classified.asof.direction, joinType === 'left', projection.columnIndices, projection.attrs);
|
|
402
|
+
log('Recognized lateral-top-1 asof: outer=%s, direction=%s, strict=%s, partitions=%d', joinType === 'left', classified.asof.direction, classified.asof.strict, classified.partition.length);
|
|
403
|
+
return asof;
|
|
404
|
+
}
|
|
405
|
+
//# sourceMappingURL=rule-lateral-top1-asof.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-lateral-top1-asof.js","sourceRoot":"","sources":["../../../../../src/planner/rules/join/rule-lateral-top1-asof.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: Monotonic Merge Join
|
|
3
|
+
*
|
|
4
|
+
* Required Characteristics:
|
|
5
|
+
* - Node must be a logical JoinNode (not already physical)
|
|
6
|
+
* - Both inputs advertise `MonotonicOn` on (at least) one of the equi-pair
|
|
7
|
+
* attributes, with matching direction (ASC for v1, since the merge-join
|
|
8
|
+
* emitter assumes ASC ordering).
|
|
9
|
+
*
|
|
10
|
+
* Why this rule exists alongside `rule-join-physical-selection`:
|
|
11
|
+
* The existing rule chooses merge-join when both sources' `physical.ordering`
|
|
12
|
+
* matches the equi-pair attribute order positionally. That misses the canonical
|
|
13
|
+
* case where a merge-join's *output* declares `monotonicOn = [l.X, r.X]` but
|
|
14
|
+
* `ordering` reflects only the left side — a parent join on `r.X` then sees
|
|
15
|
+
* the right ordering implicitly through `monotonicOn` but not through
|
|
16
|
+
* `ordering[0]`. This rule looks the equi-pair attrs up directly in
|
|
17
|
+
* `monotonicOn`, recognising those (and other future MonotonicOn-propagating
|
|
18
|
+
* paths) without sorting.
|
|
19
|
+
*
|
|
20
|
+
* Out of scope (TODO):
|
|
21
|
+
* - Composite monotonic-on prefixes (multi-key streaming merge keyed on
|
|
22
|
+
* `(X, Y)` when both sides are jointly monotonic on the prefix).
|
|
23
|
+
* - Right and full outer joins — emitter doesn't support them today.
|
|
24
|
+
* - Recognising `monotonicOn(asc)` vs `monotonicOn(desc)` by reversing one
|
|
25
|
+
* side via Sort. That defeats this rule's premise (the rule's whole point is
|
|
26
|
+
* that no sort is needed because both sides are already monotonic).
|
|
27
|
+
*/
|
|
28
|
+
import type { PlanNode } from '../../nodes/plan-node.js';
|
|
29
|
+
import type { OptContext } from '../../framework/context.js';
|
|
30
|
+
export declare function ruleMonotonicMergeJoin(node: PlanNode, _context: OptContext): PlanNode | null;
|
|
31
|
+
//# sourceMappingURL=rule-monotonic-merge-join.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-monotonic-merge-join.d.ts","sourceRoot":"","sources":["../../../../../src/planner/rules/join/rule-monotonic-merge-join.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAA6B,MAAM,0BAA0B,CAAC;AACpF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAU7D,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,GAAG,QAAQ,GAAG,IAAI,CAwF5F"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: Monotonic Merge Join
|
|
3
|
+
*
|
|
4
|
+
* Required Characteristics:
|
|
5
|
+
* - Node must be a logical JoinNode (not already physical)
|
|
6
|
+
* - Both inputs advertise `MonotonicOn` on (at least) one of the equi-pair
|
|
7
|
+
* attributes, with matching direction (ASC for v1, since the merge-join
|
|
8
|
+
* emitter assumes ASC ordering).
|
|
9
|
+
*
|
|
10
|
+
* Why this rule exists alongside `rule-join-physical-selection`:
|
|
11
|
+
* The existing rule chooses merge-join when both sources' `physical.ordering`
|
|
12
|
+
* matches the equi-pair attribute order positionally. That misses the canonical
|
|
13
|
+
* case where a merge-join's *output* declares `monotonicOn = [l.X, r.X]` but
|
|
14
|
+
* `ordering` reflects only the left side — a parent join on `r.X` then sees
|
|
15
|
+
* the right ordering implicitly through `monotonicOn` but not through
|
|
16
|
+
* `ordering[0]`. This rule looks the equi-pair attrs up directly in
|
|
17
|
+
* `monotonicOn`, recognising those (and other future MonotonicOn-propagating
|
|
18
|
+
* paths) without sorting.
|
|
19
|
+
*
|
|
20
|
+
* Out of scope (TODO):
|
|
21
|
+
* - Composite monotonic-on prefixes (multi-key streaming merge keyed on
|
|
22
|
+
* `(X, Y)` when both sides are jointly monotonic on the prefix).
|
|
23
|
+
* - Right and full outer joins — emitter doesn't support them today.
|
|
24
|
+
* - Recognising `monotonicOn(asc)` vs `monotonicOn(desc)` by reversing one
|
|
25
|
+
* side via Sort. That defeats this rule's premise (the rule's whole point is
|
|
26
|
+
* that no sort is needed because both sides are already monotonic).
|
|
27
|
+
*/
|
|
28
|
+
import { createLogger } from '../../../common/logger.js';
|
|
29
|
+
import { JoinNode } from '../../nodes/join-node.js';
|
|
30
|
+
import { MergeJoinNode } from '../../nodes/merge-join-node.js';
|
|
31
|
+
import { nestedLoopJoinCost, hashJoinCost, mergeJoinCost } from '../../cost/index.js';
|
|
32
|
+
import { PlanNodeCharacteristics } from '../../framework/characteristics.js';
|
|
33
|
+
import { extractEquiPairs, extractEquiPairsFromUsing, combineResidual, isMergeReadyOnAllPairs } from './equi-pair-extractor.js';
|
|
34
|
+
const log = createLogger('optimizer:rule:monotonic-merge-join');
|
|
35
|
+
export function ruleMonotonicMergeJoin(node, _context) {
|
|
36
|
+
if (!(node instanceof JoinNode))
|
|
37
|
+
return null;
|
|
38
|
+
const joinType = node.joinType;
|
|
39
|
+
if (joinType !== 'inner' && joinType !== 'left' && joinType !== 'semi' && joinType !== 'anti')
|
|
40
|
+
return null;
|
|
41
|
+
const leftAttrs = node.left.getAttributes();
|
|
42
|
+
const rightAttrs = node.right.getAttributes();
|
|
43
|
+
const leftAttrIds = new Set(leftAttrs.map(a => a.id));
|
|
44
|
+
const rightAttrIds = new Set(rightAttrs.map(a => a.id));
|
|
45
|
+
const extracted = node.condition
|
|
46
|
+
? extractEquiPairs(node.condition, leftAttrIds, rightAttrIds)
|
|
47
|
+
: extractEquiPairsFromUsing(node.usingColumns, leftAttrs, rightAttrs);
|
|
48
|
+
if (!extracted || extracted.equiPairs.length === 0)
|
|
49
|
+
return null;
|
|
50
|
+
// Defer to `rule-join-physical-selection` whenever both sides' physical
|
|
51
|
+
// ordering already covers ALL equi-pairs in merge-ready order. The
|
|
52
|
+
// ordering-based path produces a multi-key merge join with full
|
|
53
|
+
// unique-key propagation; demoting pairs to residual here would lose that
|
|
54
|
+
// propagation. Our rule is meant to *extend* recognition, not regress it.
|
|
55
|
+
if (isMergeReadyOnAllPairs(node.left, node.right, extracted.equiPairs))
|
|
56
|
+
return null;
|
|
57
|
+
const leftMon = PlanNodeCharacteristics.getMonotonicOn(node.left);
|
|
58
|
+
const rightMon = PlanNodeCharacteristics.getMonotonicOn(node.right);
|
|
59
|
+
if (leftMon.length === 0 || rightMon.length === 0)
|
|
60
|
+
return null;
|
|
61
|
+
// Find equi-pairs where BOTH sides are MonotonicOn on their respective
|
|
62
|
+
// attrId with matching direction. v1 requires ASC because the merge-join
|
|
63
|
+
// emitter assumes ASC; DESC-DESC streaming would need a reversed compareKeys.
|
|
64
|
+
const matchedIndices = [];
|
|
65
|
+
for (let i = 0; i < extracted.equiPairs.length; i++) {
|
|
66
|
+
const pair = extracted.equiPairs[i];
|
|
67
|
+
const l = leftMon.find(m => m.attrId === pair.leftAttrId && m.direction === 'asc');
|
|
68
|
+
if (!l)
|
|
69
|
+
continue;
|
|
70
|
+
const r = rightMon.find(m => m.attrId === pair.rightAttrId && m.direction === 'asc');
|
|
71
|
+
if (!r)
|
|
72
|
+
continue;
|
|
73
|
+
matchedIndices.push(i);
|
|
74
|
+
}
|
|
75
|
+
if (matchedIndices.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
// v1: pick a single driving equi-pair. Other equi-pairs (matched or not)
|
|
78
|
+
// must be evaluated as residual conjuncts — the merge-join emitter assumes
|
|
79
|
+
// the right side is sorted lexicographically across ALL listed equi-pair
|
|
80
|
+
// columns, which we cannot guarantee for non-driving keys.
|
|
81
|
+
// TODO: composite monotonic-on prefixes — recognise when both sides are
|
|
82
|
+
// jointly MonotonicOn on a multi-key prefix and use multiple driving keys.
|
|
83
|
+
const drivingIndex = matchedIndices[0];
|
|
84
|
+
const driving = [extracted.equiPairs[drivingIndex]];
|
|
85
|
+
// Residualize the rest. For pairs that originated from a real `=` BinaryOpNode
|
|
86
|
+
// (ON-condition path), reuse the original node. For USING-derived pairs,
|
|
87
|
+
// `equiPairNodes[i]` is undefined — bail out and let the existing
|
|
88
|
+
// ordering-based rule handle USING-with-multiple-pairs.
|
|
89
|
+
const extras = [];
|
|
90
|
+
for (let i = 0; i < extracted.equiPairs.length; i++) {
|
|
91
|
+
if (i === drivingIndex)
|
|
92
|
+
continue;
|
|
93
|
+
const orig = extracted.equiPairNodes[i];
|
|
94
|
+
if (!orig)
|
|
95
|
+
return null;
|
|
96
|
+
extras.push(orig);
|
|
97
|
+
}
|
|
98
|
+
const residual = combineResidual(extracted.residual, extras);
|
|
99
|
+
// Cost gate: even with the precondition met, hash or nested-loop may win
|
|
100
|
+
// on tiny inputs. Don't regress those.
|
|
101
|
+
const leftRows = node.left.estimatedRows ?? 100;
|
|
102
|
+
const rightRows = node.right.estimatedRows ?? 100;
|
|
103
|
+
const mergeC = mergeJoinCost(leftRows, rightRows, false, false);
|
|
104
|
+
const hashC = hashJoinCost(Math.min(leftRows, rightRows), Math.max(leftRows, rightRows));
|
|
105
|
+
const nlC = nestedLoopJoinCost(leftRows, rightRows);
|
|
106
|
+
if (Math.min(hashC, nlC) < mergeC) {
|
|
107
|
+
log('Skipping monotonic-merge: cheaper alternative exists (merge=%.2f, hash=%.2f, nl=%.2f)', mergeC, hashC, nlC);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
log('Selecting monotonic merge-join on equi-pair %d=%d (merge=%.2f, hash=%.2f, nl=%.2f)', driving[0].leftAttrId, driving[0].rightAttrId, mergeC, hashC, nlC);
|
|
111
|
+
return new MergeJoinNode(node.scope, node.left, node.right, joinType, driving, residual, node.getAttributes().slice());
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=rule-monotonic-merge-join.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-monotonic-merge-join.js","sourceRoot":"","sources":["../../../../../src/planner/rules/join/rule-monotonic-merge-join.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: Monotonic streaming window
|
|
3
|
+
*
|
|
4
|
+
* Recognizes a `WindowNode` whose source already streams in
|
|
5
|
+
* `[PARTITION BY..., ORDER BY[0]]` order — `physical.monotonicOn` covers the
|
|
6
|
+
* leading ORDER BY key and `physical.ordering` shows the partition keys as an
|
|
7
|
+
* emit-order prefix — and tags it with a `streaming` config so the runtime
|
|
8
|
+
* can switch from the buffer-and-sort path to a one-pass streaming emitter.
|
|
9
|
+
*
|
|
10
|
+
* Per-function recognition (all functions in the WindowNode must qualify; if
|
|
11
|
+
* any one falls through we keep the buffered path):
|
|
12
|
+
*
|
|
13
|
+
* - `ROW_NUMBER` / `RANK` / `DENSE_RANK`
|
|
14
|
+
* - `LAG(expr [, n [, default]])` / `LEAD(expr [, n [, default]])` with `n`
|
|
15
|
+
* being a non-negative integer literal
|
|
16
|
+
* - `FIRST_VALUE(expr)` / `LAST_VALUE(expr)` (last_value uses default frame ==
|
|
17
|
+
* current row, so it's the trivial expr-on-current-row evaluation)
|
|
18
|
+
* - `SUM` / `COUNT` / `AVG` / `MIN` / `MAX` over the default frame
|
|
19
|
+
* (`UNBOUNDED PRECEDING TO CURRENT ROW`, ROWS or RANGE — RANGE handles peer
|
|
20
|
+
* groups via delayed emit at peer boundaries)
|
|
21
|
+
* - `SUM` / `COUNT` / `AVG` / `MIN` / `MAX` / `FIRST_VALUE` / `LAST_VALUE`
|
|
22
|
+
* over a sliding frame `ROWS BETWEEN n PRECEDING AND m FOLLOWING` (literal
|
|
23
|
+
* non-negative integers `n`, `m`) or `RANGE BETWEEN <num> PRECEDING AND
|
|
24
|
+
* <num> FOLLOWING` (single numeric ORDER BY, literal non-negative offsets)
|
|
25
|
+
*
|
|
26
|
+
* Bail conditions:
|
|
27
|
+
*
|
|
28
|
+
* - leading ORDER BY key is not a trivial column reference
|
|
29
|
+
* - source's `monotonicOn` doesn't cover the leading key (or direction differs)
|
|
30
|
+
* - source's `ordering` prefix doesn't include the full ORDER BY key set
|
|
31
|
+
* - PARTITION BY columns aren't an emit-order prefix of the source ordering
|
|
32
|
+
* - any partition-by expression is non-trivial (not a column reference)
|
|
33
|
+
* - any function falls outside the recognized set, or is `DISTINCT`
|
|
34
|
+
* - frame is anything other than the default (or the explicit equivalent
|
|
35
|
+
* `UNBOUNDED PRECEDING TO CURRENT ROW` in `ROWS` or `RANGE`), or a
|
|
36
|
+
* supported sliding shape (see above)
|
|
37
|
+
*
|
|
38
|
+
* Out of scope (deferred): NTILE/PERCENT_RANK/CUME_DIST (need partition size up
|
|
39
|
+
* front), DISTINCT aggregates, asymmetric sliding shapes
|
|
40
|
+
* (`UNBOUNDED PRECEDING AND m FOLLOWING`, `n PRECEDING AND UNBOUNDED FOLLOWING`,
|
|
41
|
+
* `CURRENT ROW AND m FOLLOWING`), splitting a mixed WindowNode into streaming +
|
|
42
|
+
* buffered halves.
|
|
43
|
+
*/
|
|
44
|
+
import type { PlanNode } from '../../nodes/plan-node.js';
|
|
45
|
+
import type { OptContext } from '../../framework/context.js';
|
|
46
|
+
export declare function ruleMonotonicWindow(node: PlanNode, _context: OptContext): PlanNode | null;
|
|
47
|
+
//# sourceMappingURL=rule-monotonic-window.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-monotonic-window.d.ts","sourceRoot":"","sources":["../../../../../src/planner/rules/window/rule-monotonic-window.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AA8J7D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,GAAG,QAAQ,GAAG,IAAI,CAiJzF"}
|