@quereus/quereus 4.22.0 → 4.23.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/core/database-cardinality-feedback.d.ts +133 -20
- package/dist/src/core/database-cardinality-feedback.d.ts.map +1 -1
- package/dist/src/core/database-cardinality-feedback.js +312 -39
- package/dist/src/core/database-cardinality-feedback.js.map +1 -1
- package/dist/src/core/database.d.ts +1 -0
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +14 -6
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/core/statement.d.ts +6 -1
- package/dist/src/core/statement.d.ts.map +1 -1
- package/dist/src/core/statement.js +11 -2
- package/dist/src/core/statement.js.map +1 -1
- package/dist/src/func/builtins/cardinality-feedback.d.ts +6 -0
- package/dist/src/func/builtins/cardinality-feedback.d.ts.map +1 -1
- package/dist/src/func/builtins/cardinality-feedback.js +55 -3
- package/dist/src/func/builtins/cardinality-feedback.js.map +1 -1
- package/dist/src/func/builtins/explain.d.ts.map +1 -1
- package/dist/src/func/builtins/explain.js +3 -1
- package/dist/src/func/builtins/explain.js.map +1 -1
- package/dist/src/func/builtins/index.d.ts.map +1 -1
- package/dist/src/func/builtins/index.js +2 -1
- package/dist/src/func/builtins/index.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/planner/cost/index.d.ts +25 -19
- package/dist/src/planner/cost/index.d.ts.map +1 -1
- package/dist/src/planner/cost/index.js +28 -21
- package/dist/src/planner/cost/index.js.map +1 -1
- package/dist/src/planner/framework/trace.js +2 -2
- package/dist/src/planner/framework/trace.js.map +1 -1
- package/dist/src/planner/nodes/aggregate-node.d.ts +2 -1
- package/dist/src/planner/nodes/aggregate-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/aggregate-node.js +1 -0
- package/dist/src/planner/nodes/aggregate-node.js.map +1 -1
- package/dist/src/planner/nodes/filter.d.ts.map +1 -1
- package/dist/src/planner/nodes/filter.js +5 -10
- package/dist/src/planner/nodes/filter.js.map +1 -1
- package/dist/src/planner/nodes/hash-aggregate.d.ts +2 -1
- package/dist/src/planner/nodes/hash-aggregate.d.ts.map +1 -1
- package/dist/src/planner/nodes/hash-aggregate.js +1 -0
- package/dist/src/planner/nodes/hash-aggregate.js.map +1 -1
- package/dist/src/planner/nodes/plan-node.d.ts +11 -0
- package/dist/src/planner/nodes/plan-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/plan-node.js +3 -0
- package/dist/src/planner/nodes/plan-node.js.map +1 -1
- package/dist/src/planner/nodes/reference.d.ts +3 -3
- package/dist/src/planner/nodes/reference.d.ts.map +1 -1
- package/dist/src/planner/nodes/reference.js +11 -5
- package/dist/src/planner/nodes/reference.js.map +1 -1
- package/dist/src/planner/nodes/stream-aggregate.d.ts +2 -1
- package/dist/src/planner/nodes/stream-aggregate.d.ts.map +1 -1
- package/dist/src/planner/nodes/stream-aggregate.js +1 -0
- package/dist/src/planner/nodes/stream-aggregate.js.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.d.ts +14 -1
- package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.js +52 -20
- package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
- package/dist/src/planner/optimizer-tuning.d.ts +38 -7
- package/dist/src/planner/optimizer-tuning.d.ts.map +1 -1
- package/dist/src/planner/optimizer-tuning.js +7 -2
- package/dist/src/planner/optimizer-tuning.js.map +1 -1
- package/dist/src/planner/rules/access/rule-key-set-seek.d.ts +9 -0
- package/dist/src/planner/rules/access/rule-key-set-seek.d.ts.map +1 -1
- package/dist/src/planner/rules/access/rule-key-set-seek.js +18 -12
- package/dist/src/planner/rules/access/rule-key-set-seek.js.map +1 -1
- 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 +7 -8
- package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
- package/dist/src/planner/rules/aggregate/rule-aggregate-streaming.js +1 -1
- package/dist/src/planner/rules/aggregate/rule-aggregate-streaming.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js +7 -0
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js.map +1 -1
- package/dist/src/planner/rules/join/index-nested-loop.d.ts +6 -2
- package/dist/src/planner/rules/join/index-nested-loop.d.ts.map +1 -1
- package/dist/src/planner/rules/join/index-nested-loop.js +20 -14
- package/dist/src/planner/rules/join/index-nested-loop.js.map +1 -1
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +5 -0
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.js.map +1 -1
- 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 +102 -53
- package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts +1 -1
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.js +15 -6
- package/dist/src/planner/rules/join/rule-monotonic-merge-join.js.map +1 -1
- package/dist/src/planner/rules/join/rule-quickpick-enumeration.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-quickpick-enumeration.js +8 -1
- package/dist/src/planner/rules/join/rule-quickpick-enumeration.js.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +4 -3
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
- package/dist/src/planner/stats/catalog-stats.d.ts +8 -8
- package/dist/src/planner/stats/catalog-stats.d.ts.map +1 -1
- package/dist/src/planner/stats/catalog-stats.js +10 -10
- package/dist/src/planner/stats/catalog-stats.js.map +1 -1
- package/dist/src/planner/stats/table-cardinality.d.ts +119 -30
- package/dist/src/planner/stats/table-cardinality.d.ts.map +1 -1
- package/dist/src/planner/stats/table-cardinality.js +137 -29
- package/dist/src/planner/stats/table-cardinality.js.map +1 -1
- package/dist/src/planner/util/relative-rows.d.ts +29 -0
- package/dist/src/planner/util/relative-rows.d.ts.map +1 -0
- package/dist/src/planner/util/relative-rows.js +67 -0
- package/dist/src/planner/util/relative-rows.js.map +1 -0
- package/dist/src/planner/util/row-estimates.d.ts +23 -0
- package/dist/src/planner/util/row-estimates.d.ts.map +1 -1
- package/dist/src/planner/util/row-estimates.js +29 -0
- package/dist/src/planner/util/row-estimates.js.map +1 -1
- package/dist/src/runtime/cardinality-observations.d.ts +62 -7
- package/dist/src/runtime/cardinality-observations.d.ts.map +1 -1
- package/dist/src/runtime/cardinality-observations.js +74 -8
- package/dist/src/runtime/cardinality-observations.js.map +1 -1
- package/dist/src/runtime/emit/scan.d.ts +1 -1
- package/dist/src/runtime/emit/scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/scan.js +78 -10
- package/dist/src/runtime/emit/scan.js.map +1 -1
- package/dist/src/schema/manager.d.ts +5 -5
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +5 -5
- package/dist/src/schema/manager.js.map +1 -1
- package/dist/src/vtab/filter-info.d.ts +16 -0
- package/dist/src/vtab/filter-info.d.ts.map +1 -1
- package/dist/src/vtab/filter-info.js +29 -0
- package/dist/src/vtab/filter-info.js.map +1 -1
- package/dist/src/vtab/memory/module.d.ts.map +1 -1
- package/dist/src/vtab/memory/module.js +5 -3
- package/dist/src/vtab/memory/module.js.map +1 -1
- package/dist/src/vtab/module.d.ts +53 -0
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The base-table row-count seam.
|
|
3
3
|
*
|
|
4
|
-
* Every planner reading of a base table's size goes through {@link plannedRowCount}
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
4
|
+
* Every planner reading of a base table's size goes through {@link plannedRowCount},
|
|
5
|
+
* which ranks four sources (docs/progressive-optimizer.md §3.3): an exact count the
|
|
6
|
+
* table's module maintains (`VirtualTableModule.getRowCount`), the count observed
|
|
7
|
+
* during execution (the per-database overlay, see `core/database-cardinality-feedback.ts`),
|
|
8
|
+
* an approximate module count, the catalog's count — else `undefined`.
|
|
9
|
+
* `TableReferenceNode` (every scan's base cardinality, and the
|
|
8
10
|
* `BestAccessPlanRequest.estimatedRows` hint) and `CatalogStatsProvider.tableRows`
|
|
9
|
-
* (selectivity denominators, MV sizing) both call it
|
|
10
|
-
* stats-provider estimate cannot
|
|
11
|
+
* (selectivity denominators, MV sizing) both call it with the database's one
|
|
12
|
+
* {@link RowCountSources}, so scan cardinality and the stats-provider estimate cannot
|
|
13
|
+
* drift apart within one plan.
|
|
14
|
+
*
|
|
15
|
+
* The same sources carry the overlay's observed rows per key of an equality seek,
|
|
16
|
+
* read through {@link plannedRowsPerKey} by `IndexSeekNode` (what the seek advertises)
|
|
17
|
+
* and the index-nested-loop candidate (what one seek costs). That reader overrides only
|
|
18
|
+
* the engine's own consumers of a module's `rows` estimate; the module's cost, its
|
|
19
|
+
* `rows` and its access-path choice stay in the module's currency
|
|
20
|
+
* (docs/optimizer-costing.md § Where a module's own SELECTIVITY fits).
|
|
11
21
|
*
|
|
12
22
|
* There are exactly two spellings: `undefined` means "nobody knows" and a number —
|
|
13
23
|
* including a real 0 — means "measured". `undefined` must stay `undefined` here:
|
|
@@ -15,16 +25,18 @@
|
|
|
15
25
|
* folding a default in would change every un-analyzed table's cardinality.
|
|
16
26
|
*
|
|
17
27
|
* NOTE: the catalog `rowCount` is a snapshot from the last `ANALYZE` — rows written
|
|
18
|
-
* afterwards are invisible until the next one (or until a complete scan is observed)
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
28
|
+
* afterwards are invisible until the next one (or until a complete scan is observed),
|
|
29
|
+
* unless the module reports its own count.
|
|
30
|
+
*
|
|
31
|
+
* A real 0 (analyzed or observed empty table) is relayed as 0 everywhere a row count is
|
|
32
|
+
* reported. Sites that turn a count into a COST floor a measured 0 at one row instead
|
|
33
|
+
* (`costedRows` in `planner/util/row-estimates.ts`; the store's request sizing floors
|
|
34
|
+
* its own): a zero goes stale on the first insert, and a table priced at nothing would
|
|
35
|
+
* trade its primary-key seeks for scans (docs/optimizer-costing.md § Base-table row
|
|
36
|
+
* estimates).
|
|
27
37
|
*/
|
|
38
|
+
import { createLogger } from '../../common/logger.js';
|
|
39
|
+
const debugLog = createLogger('optimizer:stats:row-count').extend('debug');
|
|
28
40
|
/**
|
|
29
41
|
* The catalog's row count — `TableSchema.statistics`, populated by `ANALYZE` or VTab
|
|
30
42
|
* reporting.
|
|
@@ -37,21 +49,117 @@
|
|
|
37
49
|
export function catalogRowCount(table) {
|
|
38
50
|
return table.statistics?.rowCount;
|
|
39
51
|
}
|
|
52
|
+
/** The database's row-count sources: its observed overlay, and its tables' modules. */
|
|
53
|
+
export function databaseRowCountSources(db, observed) {
|
|
54
|
+
return {
|
|
55
|
+
observedRows: table => observed.observedRows(table),
|
|
56
|
+
observedRowsPerKey: (table, columns) => observed.observedRowsPerKey(table, columns),
|
|
57
|
+
reportedRows: table => reportedRowCount(db, table),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** True when `table`'s registered module implements `VirtualTableModule.getRowCount`, whether or not it answers now. */
|
|
61
|
+
export function moduleReportsRowCount(db, table) {
|
|
62
|
+
return db.schemaManager.getModule(table.vtabModuleName)?.module.getRowCount !== undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Asks `table`'s registered module for its count (`VirtualTableModule.getRowCount`).
|
|
66
|
+
* A hint, so a module without the hook, a throw, or a malformed answer (`rows` not a
|
|
67
|
+
* non-negative integer) is `undefined` — a failed hint must never fail planning.
|
|
68
|
+
*/
|
|
69
|
+
export function reportedRowCount(db, table) {
|
|
70
|
+
const module = db.schemaManager.getModule(table.vtabModuleName)?.module;
|
|
71
|
+
if (!module?.getRowCount)
|
|
72
|
+
return undefined;
|
|
73
|
+
let answer;
|
|
74
|
+
try {
|
|
75
|
+
answer = module.getRowCount(db, table);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
debugLog('Ignored row count for %s.%s: module %s threw %O', table.schemaName, table.name, table.vtabModuleName, e);
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
if (answer === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
if (!Number.isInteger(answer.rows) || answer.rows < 0) {
|
|
84
|
+
debugLog('Ignored row count for %s.%s: module %s answered %o', table.schemaName, table.name, table.vtabModuleName, answer);
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
return answer;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The base-table row count the planner uses, first answer wins:
|
|
91
|
+
*
|
|
92
|
+
* 1. an exact module count — maintained on every write, so it is the truth now and any
|
|
93
|
+
* snapshot below is older;
|
|
94
|
+
* 2. the overlay's observed count — an entry only answers for the exact `TableSchema`
|
|
95
|
+
* it was observed under and any `ANALYZE` swaps that object, so it is newer than the
|
|
96
|
+
* catalog numbers on the schema it names;
|
|
97
|
+
* 3. an approximate module count — below a measurement this session made, because an
|
|
98
|
+
* estimate can be off by more than the overlay's shift factor and only a complete
|
|
99
|
+
* scan can correct it; above the catalog, because it describes the table now;
|
|
100
|
+
* 4. the catalog (`ANALYZE`) count;
|
|
101
|
+
* 5. `undefined` (unknown).
|
|
102
|
+
*
|
|
103
|
+
* The overlay's floor then raises whatever answered from 2–4, and answers alone when
|
|
104
|
+
* nothing did (it was recorded only above the default). It never raises an exact module
|
|
105
|
+
* count: that is current, and a floor describes committed state at some earlier moment,
|
|
106
|
+
* so it cannot legitimately exceed it.
|
|
107
|
+
*
|
|
108
|
+
* The module is asked first so an exactly-sized table never touches the overlay, whose
|
|
109
|
+
* reads refresh its recency. Only the row count is overridden; column statistics are
|
|
110
|
+
* untouched.
|
|
111
|
+
*
|
|
112
|
+
* The result also becomes `BestAccessPlanRequest.estimatedRows`, so a module that reports
|
|
113
|
+
* its count prices its access path against the same number the rest of the plan uses —
|
|
114
|
+
* one size per plan.
|
|
115
|
+
*/
|
|
116
|
+
export function plannedRowCount(table, sources) {
|
|
117
|
+
const reported = sources?.reportedRows(table);
|
|
118
|
+
if (reported?.exact)
|
|
119
|
+
return reported.rows;
|
|
120
|
+
const observed = sources?.observedRows(table);
|
|
121
|
+
return raiseToFloor(observed?.rows ?? reported?.rows ?? catalogRowCount(table), observed?.atLeast);
|
|
122
|
+
}
|
|
123
|
+
function raiseToFloor(rows, floor) {
|
|
124
|
+
if (floor === undefined)
|
|
125
|
+
return rows;
|
|
126
|
+
return rows === undefined ? floor : Math.max(rows, floor);
|
|
127
|
+
}
|
|
40
128
|
/**
|
|
41
|
-
* The
|
|
42
|
-
* `undefined`
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* and
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
129
|
+
* The rows per key the planner prices a single-key equality seek on `columns` at, or
|
|
130
|
+
* `undefined` to leave the module's own estimate standing (docs/progressive-optimizer.md
|
|
131
|
+
* §3.3):
|
|
132
|
+
*
|
|
133
|
+
* 1. when the catalog's statistics cover the column set — `table.statistics` with a
|
|
134
|
+
* positive `rowCount` and a `columnStats` entry for every column — the catalog wins and
|
|
135
|
+
* nothing is overridden: the module's statistics-backed estimate is the engine's own
|
|
136
|
+
* `estimateLeaf` arithmetic over the same numbers, and the count rule (newest exact
|
|
137
|
+
* wins) does not transfer to a mean sampled under some bindings and not others.
|
|
138
|
+
* Coverage is all-or-nothing, the rule both shipped modules apply before mixing a
|
|
139
|
+
* measured factor with a shape constant;
|
|
140
|
+
* 2. otherwise the overlay's mean for the set, rounded and floored at one row — the
|
|
141
|
+
* module's `Math.max(1, …)` floor, so a key that matched nothing so far does not price a
|
|
142
|
+
* seek at nothing;
|
|
143
|
+
* 3. otherwise `undefined`.
|
|
144
|
+
*
|
|
145
|
+
* NOTE: catalog-wins is a first rule, not a measured one; an `ANALYZE` snapshot can be
|
|
146
|
+
* older than the mean. Reconsider only against a measured case where the stale catalog
|
|
147
|
+
* loses a plan the mean would have won.
|
|
53
148
|
*/
|
|
54
|
-
export function
|
|
55
|
-
|
|
149
|
+
export function plannedRowsPerKey(table, columns, sources) {
|
|
150
|
+
if (sources === undefined || catalogCoversColumns(table, columns))
|
|
151
|
+
return undefined;
|
|
152
|
+
const mean = sources.observedRowsPerKey(table, columns);
|
|
153
|
+
return mean === undefined ? undefined : Math.max(1, Math.round(mean));
|
|
154
|
+
}
|
|
155
|
+
/** True when the catalog holds a usable row count and column statistics for every column in `columns`. */
|
|
156
|
+
function catalogCoversColumns(table, columns) {
|
|
157
|
+
const stats = table.statistics;
|
|
158
|
+
if (!stats || stats.rowCount <= 0)
|
|
159
|
+
return false;
|
|
160
|
+
return columns.every(i => {
|
|
161
|
+
const name = table.columns[i]?.name;
|
|
162
|
+
return name !== undefined && stats.columnStats.has(name.toLowerCase());
|
|
163
|
+
});
|
|
56
164
|
}
|
|
57
165
|
//# sourceMappingURL=table-cardinality.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-cardinality.js","sourceRoot":"","sources":["../../../../src/planner/stats/table-cardinality.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"table-cardinality.js","sourceRoot":"","sources":["../../../../src/planner/stats/table-cardinality.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A join side's row count for RELATIVE comparison — defined even where nobody
|
|
3
|
+
* measured anything.
|
|
4
|
+
*
|
|
5
|
+
* A never-analyzed table's scan reports `undefined` (see `row-estimates.ts`), and
|
|
6
|
+
* that blank propagates: a Filter over it, a join over that, an aggregate over the
|
|
7
|
+
* join all read `undefined` too, so a consumer that defaults the blank side flat
|
|
8
|
+
* forgets that a filter has already narrowed it. This walker instead starts the
|
|
9
|
+
* unknown LEAF at the shared no-statistics default and carries that figure up
|
|
10
|
+
* through each operator with the operator's own shared formula, so a filtered
|
|
11
|
+
* side of an unknown table compares smaller than the unfiltered table beside it.
|
|
12
|
+
*
|
|
13
|
+
* The figure is relative, not an estimate the plan records: only join selection
|
|
14
|
+
* reads it, to compare candidates against each other. Everywhere else an unknown
|
|
15
|
+
* stays `undefined`, so the gates that deliberately refuse on unknown keep
|
|
16
|
+
* refusing.
|
|
17
|
+
*
|
|
18
|
+
* Lives apart from `row-estimates.ts` because it dispatches on node classes, and
|
|
19
|
+
* those node modules import `row-estimates.ts`.
|
|
20
|
+
*/
|
|
21
|
+
import type { RelationalPlanNode } from '../nodes/plan-node.js';
|
|
22
|
+
/**
|
|
23
|
+
* Rows `side` is priced at in a relative comparison: its physical estimate when
|
|
24
|
+
* known, else `unknownLeafRows` at each unknown leaf carried up through the
|
|
25
|
+
* side's operators. Floored at one row through `costedRows`, so a measured (or
|
|
26
|
+
* relayed) 0 prices the way every cost site prices it.
|
|
27
|
+
*/
|
|
28
|
+
export declare function relativeRows(side: RelationalPlanNode, unknownLeafRows: number): number;
|
|
29
|
+
//# sourceMappingURL=relative-rows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relative-rows.d.ts","sourceRoot":"","sources":["../../../../src/planner/util/relative-rows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAWhE;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAEtF"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A join side's row count for RELATIVE comparison — defined even where nobody
|
|
3
|
+
* measured anything.
|
|
4
|
+
*
|
|
5
|
+
* A never-analyzed table's scan reports `undefined` (see `row-estimates.ts`), and
|
|
6
|
+
* that blank propagates: a Filter over it, a join over that, an aggregate over the
|
|
7
|
+
* join all read `undefined` too, so a consumer that defaults the blank side flat
|
|
8
|
+
* forgets that a filter has already narrowed it. This walker instead starts the
|
|
9
|
+
* unknown LEAF at the shared no-statistics default and carries that figure up
|
|
10
|
+
* through each operator with the operator's own shared formula, so a filtered
|
|
11
|
+
* side of an unknown table compares smaller than the unfiltered table beside it.
|
|
12
|
+
*
|
|
13
|
+
* The figure is relative, not an estimate the plan records: only join selection
|
|
14
|
+
* reads it, to compare candidates against each other. Everywhere else an unknown
|
|
15
|
+
* stays `undefined`, so the gates that deliberately refuse on unknown keep
|
|
16
|
+
* refusing.
|
|
17
|
+
*
|
|
18
|
+
* Lives apart from `row-estimates.ts` because it dispatches on node classes, and
|
|
19
|
+
* those node modules import `row-estimates.ts`.
|
|
20
|
+
*/
|
|
21
|
+
import { DEFAULT_FILTER_SELECTIVITY, FilterNode } from '../nodes/filter.js';
|
|
22
|
+
import { JoinNode } from '../nodes/join-node.js';
|
|
23
|
+
import { BloomJoinNode } from '../nodes/bloom-join-node.js';
|
|
24
|
+
import { MergeJoinNode } from '../nodes/merge-join-node.js';
|
|
25
|
+
import { AggregateNode } from '../nodes/aggregate-node.js';
|
|
26
|
+
import { HashAggregateNode } from '../nodes/hash-aggregate.js';
|
|
27
|
+
import { StreamAggregateNode } from '../nodes/stream-aggregate.js';
|
|
28
|
+
import { joinPhysicalRows } from '../nodes/join-utils.js';
|
|
29
|
+
import { costedRows, filterRowsFrom, physicalSourceRows } from './row-estimates.js';
|
|
30
|
+
/**
|
|
31
|
+
* Rows `side` is priced at in a relative comparison: its physical estimate when
|
|
32
|
+
* known, else `unknownLeafRows` at each unknown leaf carried up through the
|
|
33
|
+
* side's operators. Floored at one row through `costedRows`, so a measured (or
|
|
34
|
+
* relayed) 0 prices the way every cost site prices it.
|
|
35
|
+
*/
|
|
36
|
+
export function relativeRows(side, unknownLeafRows) {
|
|
37
|
+
return costedRows(relayedRows(side, unknownLeafRows), unknownLeafRows);
|
|
38
|
+
}
|
|
39
|
+
// NOTE: recursive, unlike the iterative `PlanNode.physical` fold — it descends only
|
|
40
|
+
// through nodes with no known count, so depth is bounded by a side's unknown chain,
|
|
41
|
+
// far below the ~30,000 frames that overflow (test/planner/plan-node-traversal.spec.ts).
|
|
42
|
+
// If deep generated plans ever reach join selection with unknown leaves, convert to
|
|
43
|
+
// a worklist walk (`PlanNode.computePostOrder`).
|
|
44
|
+
function relayedRows(node, unknownLeafRows) {
|
|
45
|
+
const known = physicalSourceRows(node.physical, node);
|
|
46
|
+
if (known !== undefined)
|
|
47
|
+
return known;
|
|
48
|
+
if (node instanceof FilterNode) {
|
|
49
|
+
return filterRowsFrom(relayedRows(node.source, unknownLeafRows), node.selectivity ?? DEFAULT_FILTER_SELECTIVITY) ?? unknownLeafRows;
|
|
50
|
+
}
|
|
51
|
+
// Key coverage (`analyzeJoinKeyCoverage`) is not re-derived: the heuristic
|
|
52
|
+
// alone is close enough for a figure that is only ever compared.
|
|
53
|
+
if (node instanceof JoinNode || node instanceof BloomJoinNode || node instanceof MergeJoinNode) {
|
|
54
|
+
return joinPhysicalRows(node.joinType, undefined, relayedRows(node.left, unknownLeafRows), relayedRows(node.right, unknownLeafRows)) ?? unknownLeafRows;
|
|
55
|
+
}
|
|
56
|
+
if (node instanceof AggregateNode || node instanceof HashAggregateNode || node instanceof StreamAggregateNode) {
|
|
57
|
+
return node.rowsFrom(relayedRows(node.source, unknownLeafRows)) ?? unknownLeafRows;
|
|
58
|
+
}
|
|
59
|
+
// Any other single-relation node (Alias, Project, Sort, Cache, Distinct,
|
|
60
|
+
// LimitOffset, …) at most passes its source's rows through: an upper bound,
|
|
61
|
+
// which is all a relative comparison needs.
|
|
62
|
+
const relations = node.getRelations();
|
|
63
|
+
if (relations.length === 1)
|
|
64
|
+
return relayedRows(relations[0], unknownLeafRows);
|
|
65
|
+
return unknownLeafRows;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=relative-rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relative-rows.js","sourceRoot":"","sources":["../../../../src/planner/util/relative-rows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,0BAA0B,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEpF;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAwB,EAAE,eAAuB;IAC7E,OAAO,UAAU,CAAC,WAAW,CAAC,IAAI,EAAE,eAAe,CAAC,EAAE,eAAe,CAAC,CAAC;AACxE,CAAC;AAED,oFAAoF;AACpF,oFAAoF;AACpF,yFAAyF;AACzF,oFAAoF;AACpF,iDAAiD;AACjD,SAAS,WAAW,CAAC,IAAwB,EAAE,eAAuB;IACrE,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACtD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAEtC,IAAI,IAAI,YAAY,UAAU,EAAE,CAAC;QAChC,OAAO,cAAc,CACpB,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,EACzC,IAAI,CAAC,WAAW,IAAI,0BAA0B,CAC9C,IAAI,eAAe,CAAC;IACtB,CAAC;IACD,2EAA2E;IAC3E,iEAAiE;IACjE,IAAI,IAAI,YAAY,QAAQ,IAAI,IAAI,YAAY,aAAa,IAAI,IAAI,YAAY,aAAa,EAAE,CAAC;QAChG,OAAO,gBAAgB,CACtB,IAAI,CAAC,QAAQ,EACb,SAAS,EACT,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,EACvC,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,eAAe,CAAC,CACxC,IAAI,eAAe,CAAC;IACtB,CAAC;IACD,IAAI,IAAI,YAAY,aAAa,IAAI,IAAI,YAAY,iBAAiB,IAAI,IAAI,YAAY,mBAAmB,EAAE,CAAC;QAC/G,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,IAAI,eAAe,CAAC;IACpF,CAAC;IACD,yEAAyE;IACzE,4EAA4E;IAC5E,4CAA4C;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;IACtC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;IAC9E,OAAO,eAAe,CAAC;AACxB,CAAC"}
|
|
@@ -25,6 +25,29 @@ import type { PhysicalProperties, RelationalPlanNode } from '../nodes/plan-node.
|
|
|
25
25
|
* pre-optimization truth.
|
|
26
26
|
*/
|
|
27
27
|
export declare function physicalSourceRows(childPhysical: PhysicalProperties | undefined, source: RelationalPlanNode): number | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* A row estimate as a cost site prices it: `unknown` stands in for `undefined`
|
|
30
|
+
* (nobody measured), and a measured 0 is floored at one row. A zero is the most
|
|
31
|
+
* fragile measurement there is — any insert since invalidates it by an infinite
|
|
32
|
+
* ratio — and a table or join side priced at nothing undercuts every seek and
|
|
33
|
+
* wins every comparison it enters. One row keeps an empty table cheap without
|
|
34
|
+
* making it free. Relays keep the 0; only sites turning a count into a cost call
|
|
35
|
+
* this (docs/optimizer-costing.md § Base-table row estimates).
|
|
36
|
+
*/
|
|
37
|
+
export declare function costedRows(rows: number | undefined, unknown: number): number;
|
|
38
|
+
/**
|
|
39
|
+
* A Filter's output cardinality as a pure function of its source's and a
|
|
40
|
+
* selectivity — shared by `FilterNode`'s logical `estimatedRows` getter, its
|
|
41
|
+
* `computePhysical` (which feeds it the PHYSICAL source count), and the join
|
|
42
|
+
* rule's relative estimate (`planner/util/relative-rows.ts`) so the three cannot
|
|
43
|
+
* drift. The caller resolves the selectivity (stamped, or the last-resort
|
|
44
|
+
* `DEFAULT_FILTER_SELECTIVITY`).
|
|
45
|
+
*
|
|
46
|
+
* A non-empty source floors at one row — selectivity 0 (the provider says nothing
|
|
47
|
+
* matches) must not price the Filter at nothing — while a genuinely empty source
|
|
48
|
+
* stays 0, matching {@link aggregateRowsFrom}'s convention.
|
|
49
|
+
*/
|
|
50
|
+
export declare function filterRowsFrom(sourceRows: number | undefined, selectivity: number): number | undefined;
|
|
28
51
|
/**
|
|
29
52
|
* An aggregate's output cardinality as a pure function of its source's — shared
|
|
30
53
|
* by every aggregate node's logical `estimatedRows` getter and its
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"row-estimates.d.ts","sourceRoot":"","sources":["../../../../src/planner/util/row-estimates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEpF;;;;;;;;GAQG;AAMH,wBAAgB,kBAAkB,CACjC,aAAa,EAAE,kBAAkB,GAAG,SAAS,EAC7C,MAAM,EAAE,kBAAkB,GACxB,MAAM,GAAG,SAAS,CAEpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAChC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAapB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,QAA0B,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED,uDAAuD;AACvD,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CACnC,EAAE,EAAE,gBAAgB,EACpB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,GAAG,SAAS,CAQpB;AAED,mDAAmD;AACnD,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,UAAU,GAAG,cAAc,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAC7B,IAAI,EAAE,oBAAoB,EAC1B,UAAU,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GACzC,MAAM,GAAG,SAAS,CAoBpB"}
|
|
1
|
+
{"version":3,"file":"row-estimates.d.ts","sourceRoot":"","sources":["../../../../src/planner/util/row-estimates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEpF;;;;;;;;GAQG;AAMH,wBAAgB,kBAAkB,CACjC,aAAa,EAAE,kBAAkB,GAAG,SAAS,EAC7C,MAAM,EAAE,kBAAkB,GACxB,MAAM,GAAG,SAAS,CAEpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGtG;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAChC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAapB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,QAA0B,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED,uDAAuD;AACvD,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CACnC,EAAE,EAAE,gBAAgB,EACpB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,GAAG,SAAS,CAQpB;AAED,mDAAmD;AACnD,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,UAAU,GAAG,cAAc,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAC7B,IAAI,EAAE,oBAAoB,EAC1B,UAAU,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GACzC,MAAM,GAAG,SAAS,CAoBpB"}
|
|
@@ -31,6 +31,35 @@
|
|
|
31
31
|
export function physicalSourceRows(childPhysical, source) {
|
|
32
32
|
return childPhysical?.estimatedRows ?? source.estimatedRows;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* A row estimate as a cost site prices it: `unknown` stands in for `undefined`
|
|
36
|
+
* (nobody measured), and a measured 0 is floored at one row. A zero is the most
|
|
37
|
+
* fragile measurement there is — any insert since invalidates it by an infinite
|
|
38
|
+
* ratio — and a table or join side priced at nothing undercuts every seek and
|
|
39
|
+
* wins every comparison it enters. One row keeps an empty table cheap without
|
|
40
|
+
* making it free. Relays keep the 0; only sites turning a count into a cost call
|
|
41
|
+
* this (docs/optimizer-costing.md § Base-table row estimates).
|
|
42
|
+
*/
|
|
43
|
+
export function costedRows(rows, unknown) {
|
|
44
|
+
return rows === undefined ? unknown : Math.max(1, rows);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A Filter's output cardinality as a pure function of its source's and a
|
|
48
|
+
* selectivity — shared by `FilterNode`'s logical `estimatedRows` getter, its
|
|
49
|
+
* `computePhysical` (which feeds it the PHYSICAL source count), and the join
|
|
50
|
+
* rule's relative estimate (`planner/util/relative-rows.ts`) so the three cannot
|
|
51
|
+
* drift. The caller resolves the selectivity (stamped, or the last-resort
|
|
52
|
+
* `DEFAULT_FILTER_SELECTIVITY`).
|
|
53
|
+
*
|
|
54
|
+
* A non-empty source floors at one row — selectivity 0 (the provider says nothing
|
|
55
|
+
* matches) must not price the Filter at nothing — while a genuinely empty source
|
|
56
|
+
* stays 0, matching {@link aggregateRowsFrom}'s convention.
|
|
57
|
+
*/
|
|
58
|
+
export function filterRowsFrom(sourceRows, selectivity) {
|
|
59
|
+
if (sourceRows === undefined)
|
|
60
|
+
return undefined;
|
|
61
|
+
return sourceRows > 0 ? Math.max(1, Math.floor(sourceRows * selectivity)) : 0;
|
|
62
|
+
}
|
|
34
63
|
/**
|
|
35
64
|
* An aggregate's output cardinality as a pure function of its source's — shared
|
|
36
65
|
* by every aggregate node's logical `estimatedRows` getter and its
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"row-estimates.js","sourceRoot":"","sources":["../../../../src/planner/util/row-estimates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH;;;;;;;;GAQG;AACH,kFAAkF;AAClF,+EAA+E;AAC/E,qFAAqF;AACrF,qFAAqF;AACrF,sFAAsF;AACtF,MAAM,UAAU,kBAAkB,CACjC,aAA6C,EAC7C,MAA0B;IAE1B,OAAO,aAAa,EAAE,aAAa,IAAI,MAAM,CAAC,aAAa,CAAC;AAC7D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAChC,UAA8B,EAC9B,OAAgB,EAChB,YAAoB;IAEpB,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAE/C,6EAA6E;IAC7E,gFAAgF;IAChF,IAAI,CAAC,OAAO;QAAE,OAAO,CAAC,CAAC;IAEvB,8EAA8E;IAC9E,2EAA2E;IAC3E,yEAAyE;IACzE,IAAI,UAAU,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAE/B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC5C,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,gBAAgB,CAAC;IAChD,IAAI,IAAI,IAAI,gBAAgB;QAAE,OAAO,gBAAgB,CAAC;IACtD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;AACtC,CAAC;AAKD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,oBAAoB,CACnC,EAAoB,EACpB,QAA4B,EAC5B,SAA6B;IAE7B,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC7C,qEAAqE;IACrE,IAAI,EAAE,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACvD,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC9C,IAAI,EAAE,KAAK,WAAW;QAAE,OAAO,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;IAC/E,sDAAsD;IACtD,OAAO,gBAAgB,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;AAC/C,CAAC;AAKD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,cAAc,CAC7B,IAA0B,EAC1B,UAA2C;IAE3C,IAAI,GAAG,GAAG,IAAI,KAAK,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC/B,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QACzC,QAAQ,IAAI,EAAE,CAAC;YACd,KAAK,UAAU;gBACd,GAAG,IAAI,IAAI,CAAC;gBACZ,MAAM;YACP,KAAK,UAAU;gBACd,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBAC1B,MAAM;YACP,KAAK,cAAc;gBAClB,qEAAqE;gBACrE,mEAAmE;gBACnE,yBAAyB;gBACzB,GAAG,GAAG,gBAAgB,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;gBACnC,MAAM;QACR,CAAC;IACF,CAAC;IACD,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC;AAC9B,CAAC"}
|
|
1
|
+
{"version":3,"file":"row-estimates.js","sourceRoot":"","sources":["../../../../src/planner/util/row-estimates.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-execution sink for cardinality feedback: the
|
|
3
|
-
*
|
|
2
|
+
* Per-execution sink for cardinality feedback (docs/progressive-optimizer.md §3.1): the
|
|
3
|
+
* exact row counts of base tables a scan read in full during one execution, the
|
|
4
|
+
* floors — "holds at least N rows" — proven by scans that read only part of a table, and
|
|
5
|
+
* the rows each single-key equality seek through a non-unique index returned.
|
|
4
6
|
*
|
|
5
|
-
* The scan leaf (`runtime/emit/scan.ts`) reports here
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* whether
|
|
7
|
+
* The scan leaf (`runtime/emit/scan.ts`) reports here as each qualifying invocation
|
|
8
|
+
* ends; the run site hands the sink to `CardinalityFeedbackManager.publish` once the
|
|
9
|
+
* execution ends. Beyond dropping floors too small to ever be recorded, nothing here
|
|
10
|
+
* decides whether an observation is worth keeping — that is the overlay's record policy.
|
|
9
11
|
*/
|
|
10
12
|
import type { TableSchema } from '../schema/table.js';
|
|
13
|
+
/**
|
|
14
|
+
* What a rows-per-key site knows at emit time: which base columns its equality seek
|
|
15
|
+
* looks up, their canonical key, and the rows per key the plan was priced with. Built
|
|
16
|
+
* once per scan site, so an invocation composes no string.
|
|
17
|
+
*/
|
|
18
|
+
export interface RowsPerKeySpec {
|
|
19
|
+
/** Sorted, de-duplicated base-column indexes of the seek's equality constraints. */
|
|
20
|
+
readonly columns: readonly number[];
|
|
21
|
+
/** {@link columnSetKey} of `columns` — the overlay's key for the column set within a table. */
|
|
22
|
+
readonly key: string;
|
|
23
|
+
/**
|
|
24
|
+
* The rows per key the plan priced this seek at — the overlay's observed mean when
|
|
25
|
+
* one answered at plan time, else the module's `estimatedRows`; a new entry's baseline.
|
|
26
|
+
*/
|
|
27
|
+
readonly plannedRowsPerKey: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The overlay's key for an equality column set within one table. Shared by the scan
|
|
31
|
+
* leaf's spec and the planner's reader so the two cannot spell one set two ways.
|
|
32
|
+
*/
|
|
33
|
+
export declare function columnSetKey(columns: readonly number[]): string;
|
|
34
|
+
/** Samples of one column set's rows per key, summed within one execution. */
|
|
35
|
+
export interface RowsPerKeySamples {
|
|
36
|
+
readonly spec: RowsPerKeySpec;
|
|
37
|
+
sum: number;
|
|
38
|
+
count: number;
|
|
39
|
+
}
|
|
11
40
|
export declare class CardinalityObservations {
|
|
12
41
|
readonly rollbacksAtStart: number;
|
|
42
|
+
private readonly recordFloor;
|
|
13
43
|
/**
|
|
14
44
|
* Keyed by schema object, not name: every scan in one execution was emitted from one
|
|
15
45
|
* compile, so a nested-loop inner's re-scans and a self-join's two scan sites share
|
|
@@ -17,14 +47,39 @@ export declare class CardinalityObservations {
|
|
|
17
47
|
* per completed scan. The overlay re-keys by name when it records.
|
|
18
48
|
*/
|
|
19
49
|
private readonly counts;
|
|
50
|
+
/** Largest floor per table; same keying as `counts`. */
|
|
51
|
+
private readonly floors;
|
|
52
|
+
/**
|
|
53
|
+
* Rows-per-key samples per table and column-set key; same table keying as `counts`.
|
|
54
|
+
* One invocation is one sample: a nested-loop inner probing 10,000 outer rows adds
|
|
55
|
+
* 10,000 samples to one key here, published once as a sum and a count. Forked parallel
|
|
56
|
+
* branches share this sink by reference (`parallel-driver.ts`, `shared-sink`); the
|
|
57
|
+
* updates are synchronous map writes, so interleaving cannot lose one.
|
|
58
|
+
*/
|
|
59
|
+
private readonly perKey;
|
|
20
60
|
/**
|
|
21
61
|
* @param rollbacksAtStart The overlay's rollback count when the execution began;
|
|
22
62
|
* opaque here. If it has moved by publication, work this execution may have seen
|
|
23
63
|
* was undone under it (`CardinalityFeedbackManager.publish`).
|
|
64
|
+
* @param recordFloor The overlay's `cardinalityFeedback.minRows`: a floor at or below
|
|
65
|
+
* it can never be recorded, so it is dropped before touching a map — which is every
|
|
66
|
+
* point seek and `get()`.
|
|
24
67
|
*/
|
|
25
|
-
constructor(rollbacksAtStart: number);
|
|
68
|
+
constructor(rollbacksAtStart: number, recordFloor: number);
|
|
26
69
|
observe(table: TableSchema, rows: number): void;
|
|
70
|
+
/**
|
|
71
|
+
* One scan invocation yielded `rows` distinct rows of `table`, so it holds at least
|
|
72
|
+
* that many. Re-scans keep the largest single invocation: their rows may overlap, so
|
|
73
|
+
* a sum would overstate.
|
|
74
|
+
*/
|
|
75
|
+
observeAtLeast(table: TableSchema, rows: number): void;
|
|
76
|
+
/** One exhausted equality seek of `spec`'s column set on `table` yielded `rows` rows — one sample. */
|
|
77
|
+
observeRowsPerKey(table: TableSchema, spec: RowsPerKeySpec, rows: number): void;
|
|
27
78
|
get isEmpty(): boolean;
|
|
28
79
|
entries(): IterableIterator<[TableSchema, number]>;
|
|
80
|
+
/** Floors of tables this execution did not also count exactly — the complete count supersedes. */
|
|
81
|
+
lowerBounds(): IterableIterator<[TableSchema, number]>;
|
|
82
|
+
/** Every column set sampled this execution, with its summed rows and sample count. */
|
|
83
|
+
rowsPerKey(): IterableIterator<[TableSchema, RowsPerKeySamples]>;
|
|
29
84
|
}
|
|
30
85
|
//# sourceMappingURL=cardinality-observations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cardinality-observations.d.ts","sourceRoot":"","sources":["../../../src/runtime/cardinality-observations.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cardinality-observations.d.ts","sourceRoot":"","sources":["../../../src/runtime/cardinality-observations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,+FAA+F;IAC/F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACnC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAE/D;AAED,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,uBAAuB;IA2BvB,QAAQ,CAAC,gBAAgB,EAAE,MAAM;IAAE,OAAO,CAAC,QAAQ,CAAC,WAAW;IA1B3E;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,wDAAwD;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0D;IAEjF;;;;;;;OAOG;gBACkB,gBAAgB,EAAE,MAAM,EAAmB,WAAW,EAAE,MAAM;IAEnF,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI/C;;;;OAIG;IACH,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMtD,sGAAsG;IACtG,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAe/E,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,OAAO,IAAI,gBAAgB,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAIlD,kGAAkG;IACjG,WAAW,IAAI,gBAAgB,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAMvD,sFAAsF;IACrF,UAAU,IAAI,gBAAgB,CAAC,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;CAKjE"}
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-execution sink for cardinality feedback: the
|
|
3
|
-
*
|
|
2
|
+
* Per-execution sink for cardinality feedback (docs/progressive-optimizer.md §3.1): the
|
|
3
|
+
* exact row counts of base tables a scan read in full during one execution, the
|
|
4
|
+
* floors — "holds at least N rows" — proven by scans that read only part of a table, and
|
|
5
|
+
* the rows each single-key equality seek through a non-unique index returned.
|
|
4
6
|
*
|
|
5
|
-
* The scan leaf (`runtime/emit/scan.ts`) reports here
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* whether
|
|
7
|
+
* The scan leaf (`runtime/emit/scan.ts`) reports here as each qualifying invocation
|
|
8
|
+
* ends; the run site hands the sink to `CardinalityFeedbackManager.publish` once the
|
|
9
|
+
* execution ends. Beyond dropping floors too small to ever be recorded, nothing here
|
|
10
|
+
* decides whether an observation is worth keeping — that is the overlay's record policy.
|
|
9
11
|
*/
|
|
12
|
+
/**
|
|
13
|
+
* The overlay's key for an equality column set within one table. Shared by the scan
|
|
14
|
+
* leaf's spec and the planner's reader so the two cannot spell one set two ways.
|
|
15
|
+
*/
|
|
16
|
+
export function columnSetKey(columns) {
|
|
17
|
+
return columns.join(',');
|
|
18
|
+
}
|
|
10
19
|
export class CardinalityObservations {
|
|
11
20
|
rollbacksAtStart;
|
|
21
|
+
recordFloor;
|
|
12
22
|
/**
|
|
13
23
|
* Keyed by schema object, not name: every scan in one execution was emitted from one
|
|
14
24
|
* compile, so a nested-loop inner's re-scans and a self-join's two scan sites share
|
|
@@ -16,22 +26,78 @@ export class CardinalityObservations {
|
|
|
16
26
|
* per completed scan. The overlay re-keys by name when it records.
|
|
17
27
|
*/
|
|
18
28
|
counts = new Map();
|
|
29
|
+
/** Largest floor per table; same keying as `counts`. */
|
|
30
|
+
floors = new Map();
|
|
31
|
+
/**
|
|
32
|
+
* Rows-per-key samples per table and column-set key; same table keying as `counts`.
|
|
33
|
+
* One invocation is one sample: a nested-loop inner probing 10,000 outer rows adds
|
|
34
|
+
* 10,000 samples to one key here, published once as a sum and a count. Forked parallel
|
|
35
|
+
* branches share this sink by reference (`parallel-driver.ts`, `shared-sink`); the
|
|
36
|
+
* updates are synchronous map writes, so interleaving cannot lose one.
|
|
37
|
+
*/
|
|
38
|
+
perKey = new Map();
|
|
19
39
|
/**
|
|
20
40
|
* @param rollbacksAtStart The overlay's rollback count when the execution began;
|
|
21
41
|
* opaque here. If it has moved by publication, work this execution may have seen
|
|
22
42
|
* was undone under it (`CardinalityFeedbackManager.publish`).
|
|
43
|
+
* @param recordFloor The overlay's `cardinalityFeedback.minRows`: a floor at or below
|
|
44
|
+
* it can never be recorded, so it is dropped before touching a map — which is every
|
|
45
|
+
* point seek and `get()`.
|
|
23
46
|
*/
|
|
24
|
-
constructor(rollbacksAtStart) {
|
|
47
|
+
constructor(rollbacksAtStart, recordFloor) {
|
|
25
48
|
this.rollbacksAtStart = rollbacksAtStart;
|
|
49
|
+
this.recordFloor = recordFloor;
|
|
26
50
|
}
|
|
27
51
|
observe(table, rows) {
|
|
28
52
|
this.counts.set(table, rows);
|
|
29
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* One scan invocation yielded `rows` distinct rows of `table`, so it holds at least
|
|
56
|
+
* that many. Re-scans keep the largest single invocation: their rows may overlap, so
|
|
57
|
+
* a sum would overstate.
|
|
58
|
+
*/
|
|
59
|
+
observeAtLeast(table, rows) {
|
|
60
|
+
if (rows <= this.recordFloor)
|
|
61
|
+
return;
|
|
62
|
+
const known = this.floors.get(table);
|
|
63
|
+
if (known === undefined || rows > known)
|
|
64
|
+
this.floors.set(table, rows);
|
|
65
|
+
}
|
|
66
|
+
/** One exhausted equality seek of `spec`'s column set on `table` yielded `rows` rows — one sample. */
|
|
67
|
+
observeRowsPerKey(table, spec, rows) {
|
|
68
|
+
let byKey = this.perKey.get(table);
|
|
69
|
+
if (!byKey) {
|
|
70
|
+
byKey = new Map();
|
|
71
|
+
this.perKey.set(table, byKey);
|
|
72
|
+
}
|
|
73
|
+
const samples = byKey.get(spec.key);
|
|
74
|
+
if (samples) {
|
|
75
|
+
samples.sum += rows;
|
|
76
|
+
samples.count++;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
byKey.set(spec.key, { spec, sum: rows, count: 1 });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
30
82
|
get isEmpty() {
|
|
31
|
-
return this.counts.size === 0;
|
|
83
|
+
return this.counts.size === 0 && this.floors.size === 0 && this.perKey.size === 0;
|
|
32
84
|
}
|
|
33
85
|
entries() {
|
|
34
86
|
return this.counts.entries();
|
|
35
87
|
}
|
|
88
|
+
/** Floors of tables this execution did not also count exactly — the complete count supersedes. */
|
|
89
|
+
*lowerBounds() {
|
|
90
|
+
for (const entry of this.floors) {
|
|
91
|
+
if (!this.counts.has(entry[0]))
|
|
92
|
+
yield entry;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Every column set sampled this execution, with its summed rows and sample count. */
|
|
96
|
+
*rowsPerKey() {
|
|
97
|
+
for (const [table, byKey] of this.perKey) {
|
|
98
|
+
for (const samples of byKey.values())
|
|
99
|
+
yield [table, samples];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
36
102
|
}
|
|
37
103
|
//# sourceMappingURL=cardinality-observations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cardinality-observations.js","sourceRoot":"","sources":["../../../src/runtime/cardinality-observations.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cardinality-observations.js","sourceRoot":"","sources":["../../../src/runtime/cardinality-observations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAqBH;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAA0B;IACtD,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC;AASD,MAAM,OAAO,uBAAuB;IA2Bd;IAA2C;IA1BhE;;;;;OAKG;IACc,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IACzD,wDAAwD;IACvC,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IACzD;;;;;;OAMG;IACc,MAAM,GAAG,IAAI,GAAG,EAA+C,CAAC;IAEjF;;;;;;;OAOG;IACH,YAAqB,gBAAwB,EAAmB,WAAmB;QAA9D,qBAAgB,GAAhB,gBAAgB,CAAQ;QAAmB,gBAAW,GAAX,WAAW,CAAQ;IAAG,CAAC;IAEvF,OAAO,CAAC,KAAkB,EAAE,IAAY;QACvC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,cAAc,CAAC,KAAkB,EAAE,IAAY;QAC9C,IAAI,IAAI,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,GAAG,KAAK;YAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACvE,CAAC;IAED,sGAAsG;IACtG,iBAAiB,CAAC,KAAkB,EAAE,IAAoB,EAAE,IAAY;QACvE,IAAI,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC;YAClB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;YACpB,OAAO,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACpD,CAAC;IACF,CAAC;IAED,IAAI,OAAO;QACV,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC;IACnF,CAAC;IAED,OAAO;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;IAC9B,CAAC;IAED,kGAAkG;IAClG,CAAC,WAAW;QACX,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAAE,MAAM,KAAK,CAAC;QAC7C,CAAC;IACF,CAAC;IAED,sFAAsF;IACtF,CAAC,UAAU;QACV,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1C,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE;gBAAE,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC9D,CAAC;IACF,CAAC;CACD"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type SqlValue } from "../../common/types.js";
|
|
2
2
|
import { SeqScanNode, IndexScanNode, IndexSeekNode } from "../../planner/nodes/table-access-nodes.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type FilterInfo } from "../../vtab/filter-info.js";
|
|
4
4
|
import type { Instruction, RuntimeContext } from "../types.js";
|
|
5
5
|
import type { EmissionContext } from "../emission-context.js";
|
|
6
6
|
/**
|