@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.
Files changed (131) hide show
  1. package/dist/src/core/database-cardinality-feedback.d.ts +133 -20
  2. package/dist/src/core/database-cardinality-feedback.d.ts.map +1 -1
  3. package/dist/src/core/database-cardinality-feedback.js +312 -39
  4. package/dist/src/core/database-cardinality-feedback.js.map +1 -1
  5. package/dist/src/core/database.d.ts +1 -0
  6. package/dist/src/core/database.d.ts.map +1 -1
  7. package/dist/src/core/database.js +14 -6
  8. package/dist/src/core/database.js.map +1 -1
  9. package/dist/src/core/statement.d.ts +6 -1
  10. package/dist/src/core/statement.d.ts.map +1 -1
  11. package/dist/src/core/statement.js +11 -2
  12. package/dist/src/core/statement.js.map +1 -1
  13. package/dist/src/func/builtins/cardinality-feedback.d.ts +6 -0
  14. package/dist/src/func/builtins/cardinality-feedback.d.ts.map +1 -1
  15. package/dist/src/func/builtins/cardinality-feedback.js +55 -3
  16. package/dist/src/func/builtins/cardinality-feedback.js.map +1 -1
  17. package/dist/src/func/builtins/explain.d.ts.map +1 -1
  18. package/dist/src/func/builtins/explain.js +3 -1
  19. package/dist/src/func/builtins/explain.js.map +1 -1
  20. package/dist/src/func/builtins/index.d.ts.map +1 -1
  21. package/dist/src/func/builtins/index.js +2 -1
  22. package/dist/src/func/builtins/index.js.map +1 -1
  23. package/dist/src/index.d.ts +1 -1
  24. package/dist/src/index.d.ts.map +1 -1
  25. package/dist/src/planner/cost/index.d.ts +25 -19
  26. package/dist/src/planner/cost/index.d.ts.map +1 -1
  27. package/dist/src/planner/cost/index.js +28 -21
  28. package/dist/src/planner/cost/index.js.map +1 -1
  29. package/dist/src/planner/framework/trace.js +2 -2
  30. package/dist/src/planner/framework/trace.js.map +1 -1
  31. package/dist/src/planner/nodes/aggregate-node.d.ts +2 -1
  32. package/dist/src/planner/nodes/aggregate-node.d.ts.map +1 -1
  33. package/dist/src/planner/nodes/aggregate-node.js +1 -0
  34. package/dist/src/planner/nodes/aggregate-node.js.map +1 -1
  35. package/dist/src/planner/nodes/filter.d.ts.map +1 -1
  36. package/dist/src/planner/nodes/filter.js +5 -10
  37. package/dist/src/planner/nodes/filter.js.map +1 -1
  38. package/dist/src/planner/nodes/hash-aggregate.d.ts +2 -1
  39. package/dist/src/planner/nodes/hash-aggregate.d.ts.map +1 -1
  40. package/dist/src/planner/nodes/hash-aggregate.js +1 -0
  41. package/dist/src/planner/nodes/hash-aggregate.js.map +1 -1
  42. package/dist/src/planner/nodes/plan-node.d.ts +11 -0
  43. package/dist/src/planner/nodes/plan-node.d.ts.map +1 -1
  44. package/dist/src/planner/nodes/plan-node.js +3 -0
  45. package/dist/src/planner/nodes/plan-node.js.map +1 -1
  46. package/dist/src/planner/nodes/reference.d.ts +3 -3
  47. package/dist/src/planner/nodes/reference.d.ts.map +1 -1
  48. package/dist/src/planner/nodes/reference.js +11 -5
  49. package/dist/src/planner/nodes/reference.js.map +1 -1
  50. package/dist/src/planner/nodes/stream-aggregate.d.ts +2 -1
  51. package/dist/src/planner/nodes/stream-aggregate.d.ts.map +1 -1
  52. package/dist/src/planner/nodes/stream-aggregate.js +1 -0
  53. package/dist/src/planner/nodes/stream-aggregate.js.map +1 -1
  54. package/dist/src/planner/nodes/table-access-nodes.d.ts +14 -1
  55. package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
  56. package/dist/src/planner/nodes/table-access-nodes.js +52 -20
  57. package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
  58. package/dist/src/planner/optimizer-tuning.d.ts +38 -7
  59. package/dist/src/planner/optimizer-tuning.d.ts.map +1 -1
  60. package/dist/src/planner/optimizer-tuning.js +7 -2
  61. package/dist/src/planner/optimizer-tuning.js.map +1 -1
  62. package/dist/src/planner/rules/access/rule-key-set-seek.d.ts +9 -0
  63. package/dist/src/planner/rules/access/rule-key-set-seek.d.ts.map +1 -1
  64. package/dist/src/planner/rules/access/rule-key-set-seek.js +18 -12
  65. package/dist/src/planner/rules/access/rule-key-set-seek.js.map +1 -1
  66. package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
  67. package/dist/src/planner/rules/access/rule-select-access-path.js +7 -8
  68. package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
  69. package/dist/src/planner/rules/aggregate/rule-aggregate-streaming.js +1 -1
  70. package/dist/src/planner/rules/aggregate/rule-aggregate-streaming.js.map +1 -1
  71. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts.map +1 -1
  72. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js +7 -0
  73. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js.map +1 -1
  74. package/dist/src/planner/rules/join/index-nested-loop.d.ts +6 -2
  75. package/dist/src/planner/rules/join/index-nested-loop.d.ts.map +1 -1
  76. package/dist/src/planner/rules/join/index-nested-loop.js +20 -14
  77. package/dist/src/planner/rules/join/index-nested-loop.js.map +1 -1
  78. package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts.map +1 -1
  79. package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +5 -0
  80. package/dist/src/planner/rules/join/rule-fanout-batched-outer.js.map +1 -1
  81. package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts.map +1 -1
  82. package/dist/src/planner/rules/join/rule-join-physical-selection.js +102 -53
  83. package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
  84. package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts +1 -1
  85. package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts.map +1 -1
  86. package/dist/src/planner/rules/join/rule-monotonic-merge-join.js +15 -6
  87. package/dist/src/planner/rules/join/rule-monotonic-merge-join.js.map +1 -1
  88. package/dist/src/planner/rules/join/rule-quickpick-enumeration.d.ts.map +1 -1
  89. package/dist/src/planner/rules/join/rule-quickpick-enumeration.js +8 -1
  90. package/dist/src/planner/rules/join/rule-quickpick-enumeration.js.map +1 -1
  91. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts.map +1 -1
  92. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +4 -3
  93. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
  94. package/dist/src/planner/stats/catalog-stats.d.ts +8 -8
  95. package/dist/src/planner/stats/catalog-stats.d.ts.map +1 -1
  96. package/dist/src/planner/stats/catalog-stats.js +10 -10
  97. package/dist/src/planner/stats/catalog-stats.js.map +1 -1
  98. package/dist/src/planner/stats/table-cardinality.d.ts +119 -30
  99. package/dist/src/planner/stats/table-cardinality.d.ts.map +1 -1
  100. package/dist/src/planner/stats/table-cardinality.js +137 -29
  101. package/dist/src/planner/stats/table-cardinality.js.map +1 -1
  102. package/dist/src/planner/util/relative-rows.d.ts +29 -0
  103. package/dist/src/planner/util/relative-rows.d.ts.map +1 -0
  104. package/dist/src/planner/util/relative-rows.js +67 -0
  105. package/dist/src/planner/util/relative-rows.js.map +1 -0
  106. package/dist/src/planner/util/row-estimates.d.ts +23 -0
  107. package/dist/src/planner/util/row-estimates.d.ts.map +1 -1
  108. package/dist/src/planner/util/row-estimates.js +29 -0
  109. package/dist/src/planner/util/row-estimates.js.map +1 -1
  110. package/dist/src/runtime/cardinality-observations.d.ts +62 -7
  111. package/dist/src/runtime/cardinality-observations.d.ts.map +1 -1
  112. package/dist/src/runtime/cardinality-observations.js +74 -8
  113. package/dist/src/runtime/cardinality-observations.js.map +1 -1
  114. package/dist/src/runtime/emit/scan.d.ts +1 -1
  115. package/dist/src/runtime/emit/scan.d.ts.map +1 -1
  116. package/dist/src/runtime/emit/scan.js +78 -10
  117. package/dist/src/runtime/emit/scan.js.map +1 -1
  118. package/dist/src/schema/manager.d.ts +5 -5
  119. package/dist/src/schema/manager.d.ts.map +1 -1
  120. package/dist/src/schema/manager.js +5 -5
  121. package/dist/src/schema/manager.js.map +1 -1
  122. package/dist/src/vtab/filter-info.d.ts +16 -0
  123. package/dist/src/vtab/filter-info.d.ts.map +1 -1
  124. package/dist/src/vtab/filter-info.js +29 -0
  125. package/dist/src/vtab/filter-info.js.map +1 -1
  126. package/dist/src/vtab/memory/module.d.ts.map +1 -1
  127. package/dist/src/vtab/memory/module.js +5 -3
  128. package/dist/src/vtab/memory/module.js.map +1 -1
  129. package/dist/src/vtab/module.d.ts +53 -0
  130. package/dist/src/vtab/module.d.ts.map +1 -1
  131. 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
- * the count observed during execution (the per-database overlay, see
6
- * `core/database-cardinality-feedback.ts`), else the catalog's count, else
7
- * `undefined`. `TableReferenceNode` (every scan's base cardinality, and the
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, so scan cardinality and the
10
- * stats-provider estimate cannot drift apart within one plan.
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
- * NOTE: a real 0 (analyzed or observed empty table) is distinct from `undefined`,
21
- * but the `estimatedRows || default` spellings in `rule-select-access-path`,
22
- * `rule-grow-retrieve` and `IndexSeekNode.computePhysical` collapse the two and
23
- * substitute their default — so a measured empty table reaches `getBestAccessPlan`
24
- * as "unknown" (1000) while a SeqScan over it reports 0. Harmless today (any plan
25
- * over 0 rows is cheap); switch those to `??` if an empty-table plan ever costs out
26
- * wrong.
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 base-table row count the planner uses: observed, else catalog (`ANALYZE`), else
42
- * `undefined` (unknown). The overlay wins because an entry only answers for the exact
43
- * `TableSchema` it was observed under and any `ANALYZE` swaps that object — an
44
- * answering entry is always newer than the catalog numbers on the schema it names.
45
- * Only the row count is overridden; column statistics are untouched.
46
- *
47
- * NOTE: accepted tradeoff — the result also becomes `BestAccessPlanRequest.estimatedRows`,
48
- * and a self-sizing module (`StoreModule.sizeRequestFromLiveCount`) honors a supplied
49
- * hint over its own running count, so between full scans it prices against the last
50
- * observed size instead of its live one; one size per plan weighed over the fresher
51
- * live count. Revisit when a module's live size reaches the planner
52
- * (`feat-planner-asks-the-backend-how-big-a-table-is`) — it should outrank the overlay.
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 plannedRowCount(table, observed) {
55
- return observed?.observedRows(table) ?? catalogRowCount(table);
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;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAaH;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,KAAkB;IACjD,OAAO,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAAC,KAAkB,EAAE,QAAuC;IAC1F,OAAO,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC"}
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 exact row counts of base tables
3
- * that a scan read in full during one execution (docs/progressive-optimizer.md §3.1).
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 when a qualifying scan runs to
6
- * natural exhaustion; the run site hands the sink to
7
- * `CardinalityFeedbackManager.publish` once the execution ends. Nothing here decides
8
- * whether a count is worth keeping — that is the overlay's record policy.
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;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,qBAAa,uBAAuB;IAcvB,QAAQ,CAAC,gBAAgB,EAAE,MAAM;IAb7C;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IAEzD;;;;OAIG;gBACkB,gBAAgB,EAAE,MAAM;IAE7C,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI/C,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,OAAO,IAAI,gBAAgB,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;CAGlD"}
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 exact row counts of base tables
3
- * that a scan read in full during one execution (docs/progressive-optimizer.md §3.1).
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 when a qualifying scan runs to
6
- * natural exhaustion; the run site hands the sink to
7
- * `CardinalityFeedbackManager.publish` once the execution ends. Nothing here decides
8
- * whether a count is worth keeping — that is the overlay's record policy.
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;;;;;;;;GAQG;AAIH,MAAM,OAAO,uBAAuB;IAcd;IAbrB;;;;;OAKG;IACc,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAEzD;;;;OAIG;IACH,YAAqB,gBAAwB;QAAxB,qBAAgB,GAAhB,gBAAgB,CAAQ;IAAG,CAAC;IAEjD,OAAO,CAAC,KAAkB,EAAE,IAAY;QACvC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,IAAI,OAAO;QACV,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;IAC9B,CAAC;CACD"}
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 { FilterInfo } from "../../vtab/filter-info.js";
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
  /**