@quereus/quereus 4.16.0 → 4.17.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 (163) hide show
  1. package/dist/src/core/database-auto-analyze.d.ts +258 -0
  2. package/dist/src/core/database-auto-analyze.d.ts.map +1 -0
  3. package/dist/src/core/database-auto-analyze.js +616 -0
  4. package/dist/src/core/database-auto-analyze.js.map +1 -0
  5. package/dist/src/core/database-materialized-views.d.ts +42 -1
  6. package/dist/src/core/database-materialized-views.d.ts.map +1 -1
  7. package/dist/src/core/database-materialized-views.js +76 -3
  8. package/dist/src/core/database-materialized-views.js.map +1 -1
  9. package/dist/src/core/database-transaction.d.ts +17 -0
  10. package/dist/src/core/database-transaction.d.ts.map +1 -1
  11. package/dist/src/core/database-transaction.js +37 -0
  12. package/dist/src/core/database-transaction.js.map +1 -1
  13. package/dist/src/core/database.d.ts +8 -1
  14. package/dist/src/core/database.d.ts.map +1 -1
  15. package/dist/src/core/database.js +135 -5
  16. package/dist/src/core/database.js.map +1 -1
  17. package/dist/src/planner/analysis/change-scope.d.ts.map +1 -1
  18. package/dist/src/planner/analysis/change-scope.js +8 -8
  19. package/dist/src/planner/analysis/change-scope.js.map +1 -1
  20. package/dist/src/planner/analysis/constraint-extractor.d.ts.map +1 -1
  21. package/dist/src/planner/analysis/constraint-extractor.js +27 -14
  22. package/dist/src/planner/analysis/constraint-extractor.js.map +1 -1
  23. package/dist/src/planner/analysis/expression-fingerprint.d.ts.map +1 -1
  24. package/dist/src/planner/analysis/expression-fingerprint.js +23 -2
  25. package/dist/src/planner/analysis/expression-fingerprint.js.map +1 -1
  26. package/dist/src/planner/analysis/mv-body-functions.d.ts +46 -0
  27. package/dist/src/planner/analysis/mv-body-functions.d.ts.map +1 -0
  28. package/dist/src/planner/analysis/mv-body-functions.js +65 -0
  29. package/dist/src/planner/analysis/mv-body-functions.js.map +1 -0
  30. package/dist/src/planner/analysis/predicate-shape.d.ts +32 -4
  31. package/dist/src/planner/analysis/predicate-shape.d.ts.map +1 -1
  32. package/dist/src/planner/analysis/predicate-shape.js +37 -5
  33. package/dist/src/planner/analysis/predicate-shape.js.map +1 -1
  34. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +2 -2
  35. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
  36. package/dist/src/planner/analysis/query-rewrite-matcher.js +46 -6
  37. package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -1
  38. package/dist/src/planner/analysis/sat-checker.js +2 -5
  39. package/dist/src/planner/analysis/sat-checker.js.map +1 -1
  40. package/dist/src/planner/building/select-aggregates.d.ts +17 -5
  41. package/dist/src/planner/building/select-aggregates.d.ts.map +1 -1
  42. package/dist/src/planner/building/select-aggregates.js +122 -79
  43. package/dist/src/planner/building/select-aggregates.js.map +1 -1
  44. package/dist/src/planner/building/select-modifiers.d.ts +1 -1
  45. package/dist/src/planner/building/select.d.ts.map +1 -1
  46. package/dist/src/planner/building/select.js +73 -55
  47. package/dist/src/planner/building/select.js.map +1 -1
  48. package/dist/src/planner/cost/index.d.ts +14 -0
  49. package/dist/src/planner/cost/index.d.ts.map +1 -1
  50. package/dist/src/planner/cost/index.js +19 -0
  51. package/dist/src/planner/cost/index.js.map +1 -1
  52. package/dist/src/planner/nodes/limit-offset.d.ts.map +1 -1
  53. package/dist/src/planner/nodes/limit-offset.js +3 -2
  54. package/dist/src/planner/nodes/limit-offset.js.map +1 -1
  55. package/dist/src/planner/optimizer-tuning.d.ts +10 -6
  56. package/dist/src/planner/optimizer-tuning.d.ts.map +1 -1
  57. package/dist/src/planner/optimizer-tuning.js.map +1 -1
  58. package/dist/src/planner/optimizer.d.ts.map +1 -1
  59. package/dist/src/planner/optimizer.js +24 -0
  60. package/dist/src/planner/optimizer.js.map +1 -1
  61. package/dist/src/planner/rules/access/rule-monotonic-range-access.d.ts.map +1 -1
  62. package/dist/src/planner/rules/access/rule-monotonic-range-access.js +8 -4
  63. package/dist/src/planner/rules/access/rule-monotonic-range-access.js.map +1 -1
  64. package/dist/src/planner/rules/access/rule-select-access-path.d.ts +14 -0
  65. package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
  66. package/dist/src/planner/rules/access/rule-select-access-path.js +5 -1
  67. package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
  68. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.d.ts +3 -1
  69. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.d.ts.map +1 -1
  70. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js +8 -3
  71. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js.map +1 -1
  72. package/dist/src/planner/rules/aggregate/rule-minmax-index-boundary.d.ts +47 -0
  73. package/dist/src/planner/rules/aggregate/rule-minmax-index-boundary.d.ts.map +1 -0
  74. package/dist/src/planner/rules/aggregate/rule-minmax-index-boundary.js +200 -0
  75. package/dist/src/planner/rules/aggregate/rule-minmax-index-boundary.js.map +1 -0
  76. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts +29 -0
  77. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts.map +1 -1
  78. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js +55 -16
  79. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js.map +1 -1
  80. package/dist/src/planner/rules/join/index-nested-loop.d.ts +96 -22
  81. package/dist/src/planner/rules/join/index-nested-loop.d.ts.map +1 -1
  82. package/dist/src/planner/rules/join/index-nested-loop.js +401 -82
  83. package/dist/src/planner/rules/join/index-nested-loop.js.map +1 -1
  84. package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts +20 -0
  85. package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts.map +1 -1
  86. package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +23 -4
  87. package/dist/src/planner/rules/join/rule-fanout-batched-outer.js.map +1 -1
  88. package/dist/src/planner/rules/join/rule-join-greedy-commute.d.ts.map +1 -1
  89. package/dist/src/planner/rules/join/rule-join-greedy-commute.js +7 -0
  90. package/dist/src/planner/rules/join/rule-join-greedy-commute.js.map +1 -1
  91. package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts +19 -2
  92. package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts.map +1 -1
  93. package/dist/src/planner/rules/join/rule-join-physical-selection.js +262 -40
  94. package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
  95. package/dist/src/planner/rules/predicate/rule-sargable-range-rewrite.d.ts.map +1 -1
  96. package/dist/src/planner/rules/predicate/rule-sargable-range-rewrite.js +2 -1
  97. package/dist/src/planner/rules/predicate/rule-sargable-range-rewrite.js.map +1 -1
  98. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts +21 -0
  99. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts.map +1 -1
  100. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +24 -6
  101. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
  102. package/dist/src/planner/rules/shared/access-leaf.d.ts +12 -12
  103. package/dist/src/planner/rules/shared/access-leaf.d.ts.map +1 -1
  104. package/dist/src/planner/rules/shared/access-leaf.js +3 -14
  105. package/dist/src/planner/rules/shared/access-leaf.js.map +1 -1
  106. package/dist/src/planner/stats/catalog-stats.d.ts.map +1 -1
  107. package/dist/src/planner/stats/catalog-stats.js +6 -10
  108. package/dist/src/planner/stats/catalog-stats.js.map +1 -1
  109. package/dist/src/planner/type-utils.d.ts.map +1 -1
  110. package/dist/src/planner/type-utils.js +3 -5
  111. package/dist/src/planner/type-utils.js.map +1 -1
  112. package/dist/src/planner/util/fd-utils.d.ts.map +1 -1
  113. package/dist/src/planner/util/fd-utils.js +4 -7
  114. package/dist/src/planner/util/fd-utils.js.map +1 -1
  115. package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
  116. package/dist/src/runtime/emit/alter-table.js +58 -4
  117. package/dist/src/runtime/emit/alter-table.js.map +1 -1
  118. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  119. package/dist/src/runtime/emit/binary.js +5 -3
  120. package/dist/src/runtime/emit/binary.js.map +1 -1
  121. package/dist/src/runtime/emit/fanout-lookup-join.d.ts.map +1 -1
  122. package/dist/src/runtime/emit/fanout-lookup-join.js +9 -1
  123. package/dist/src/runtime/emit/fanout-lookup-join.js.map +1 -1
  124. package/dist/src/runtime/emit/limit-offset.d.ts.map +1 -1
  125. package/dist/src/runtime/emit/limit-offset.js +50 -24
  126. package/dist/src/runtime/emit/limit-offset.js.map +1 -1
  127. package/dist/src/runtime/emit/materialized-view-helpers.d.ts +33 -2
  128. package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
  129. package/dist/src/runtime/emit/materialized-view-helpers.js +61 -5
  130. package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
  131. package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
  132. package/dist/src/runtime/emit/materialized-view.js +6 -1
  133. package/dist/src/runtime/emit/materialized-view.js.map +1 -1
  134. package/dist/src/runtime/emit/ordinal-slice.d.ts.map +1 -1
  135. package/dist/src/runtime/emit/ordinal-slice.js +8 -0
  136. package/dist/src/runtime/emit/ordinal-slice.js.map +1 -1
  137. package/dist/src/schema/derivation.d.ts +15 -1
  138. package/dist/src/schema/derivation.d.ts.map +1 -1
  139. package/dist/src/schema/derivation.js.map +1 -1
  140. package/dist/src/schema/function.d.ts +9 -4
  141. package/dist/src/schema/function.d.ts.map +1 -1
  142. package/dist/src/schema/function.js +11 -9
  143. package/dist/src/schema/function.js.map +1 -1
  144. package/dist/src/schema/schema.d.ts +14 -3
  145. package/dist/src/schema/schema.d.ts.map +1 -1
  146. package/dist/src/schema/schema.js +20 -2
  147. package/dist/src/schema/schema.js.map +1 -1
  148. package/dist/src/schema/table.d.ts +27 -0
  149. package/dist/src/schema/table.d.ts.map +1 -1
  150. package/dist/src/schema/table.js +55 -0
  151. package/dist/src/schema/table.js.map +1 -1
  152. package/dist/src/schema/unique-enforcement.d.ts +6 -13
  153. package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
  154. package/dist/src/schema/unique-enforcement.js +8 -17
  155. package/dist/src/schema/unique-enforcement.js.map +1 -1
  156. package/dist/src/util/comparison.d.ts +19 -0
  157. package/dist/src/util/comparison.d.ts.map +1 -1
  158. package/dist/src/util/comparison.js +22 -0
  159. package/dist/src/util/comparison.js.map +1 -1
  160. package/dist/src/vtab/memory/module.d.ts.map +1 -1
  161. package/dist/src/vtab/memory/module.js +10 -0
  162. package/dist/src/vtab/memory/module.js.map +1 -1
  163. package/package.json +3 -3
@@ -0,0 +1,616 @@
1
+ /**
2
+ * Auto-analyze staleness bookkeeping and background statistics refresh.
3
+ *
4
+ * Tracks, per base table, how many distinct rows committed transactions have
5
+ * changed since statistics were last collected for that table, decides when that
6
+ * drift is large enough to call the table's statistics stale, and — off the write
7
+ * path, from a debounced timer — refreshes them by running the ordinary `ANALYZE`
8
+ * statement for that one table.
9
+ *
10
+ * The counts come from the transaction change log (`TransactionManager`), read
11
+ * post-commit while the log is still alive. That log is maintained
12
+ * unconditionally on every write path already, so counting costs one `Map.size`
13
+ * read per table per savepoint layer and nothing on the write path itself.
14
+ *
15
+ * The refresh goes through `db.exec('analyze …')` rather than reaching into the
16
+ * collection internals. Two things fall out of that:
17
+ *
18
+ * - `db.exec` acquires the database's execution mutex, which is what serializes
19
+ * all statement execution. A refresh driven from a timer therefore queues
20
+ * behind whatever is running instead of racing it. It cannot deadlock, because
21
+ * it is never started from inside a statement.
22
+ * - There is exactly one implementation of "collect and record statistics for a
23
+ * table" (`runtime/emit/analyze.ts`), including its per-table `try` that logs
24
+ * and continues.
25
+ *
26
+ * A refresh cannot trigger itself: `ANALYZE` performs no DML, and it publishes
27
+ * its result by swapping the `TableSchema` and firing the internal catalog
28
+ * notifier rather than the data-change channel. Its implicit transaction commits
29
+ * with an empty change log, so no counter advances.
30
+ */
31
+ import { createLogger } from '../common/logger.js';
32
+ import { quoteIdentifier } from '../emit/ast-stringify.js';
33
+ import { catalogRowCount } from '../planner/stats/table-cardinality.js';
34
+ import { splitBaseKey } from '../util/qualified-name.js';
35
+ const log = createLogger('core:auto-analyze');
36
+ const debugLog = log.extend('debug');
37
+ const warnLog = log.extend('warn');
38
+ /**
39
+ * How long a crossing waits before its refresh starts. Long enough that a burst
40
+ * of small commits collapses into one refresh, short enough that a test or an
41
+ * interactive session does not notice it.
42
+ */
43
+ const AUTO_ANALYZE_DEBOUNCE_MS = 50;
44
+ /**
45
+ * A table may spend at most `1 / AUTO_ANALYZE_DUTY_CYCLE` of wall-clock time in
46
+ * automatic `ANALYZE`: after a refresh that took `t` ms, the next one for that
47
+ * table may not start for `t × AUTO_ANALYZE_DUTY_CYCLE` ms.
48
+ *
49
+ * NOTE: this cooldown is load-bearing, not decoration. Without it a bulk load
50
+ * re-analyzes on a geometric ladder — a refresh at 50k rows records
51
+ * `analyzedRowCount = 50k`, so the next threshold is `ratio × 50k = 10k` further
52
+ * rows, and the scan that follows visits 60k, then 72k, then 86k… Each scan is
53
+ * O(rows) and they compound. Capping the duty cycle bounds the background cost to
54
+ * a fixed fraction of one core regardless of write rate, without having to model
55
+ * the workload. Do not remove it as "an unnecessary timer".
56
+ */
57
+ const AUTO_ANALYZE_DUTY_CYCLE = 10;
58
+ /** Floor on the cooldown after a *failed* refresh, so a permanently unreadable
59
+ * table cannot spin (a fast failure has a near-zero duty-cycle cooldown). */
60
+ const AUTO_ANALYZE_FAILURE_BACKOFF_MS = 5000;
61
+ /**
62
+ * First delay before retrying a refresh that was deferred because a transaction
63
+ * was open, doubling on each further retry (250, 500, 1000, 2000 ms).
64
+ *
65
+ * NOT the debounce: the debounce exists to let a burst of commits collapse, and
66
+ * 50 ms is far too soon to expect a statement that was in flight a moment ago to
67
+ * have finished. Starting an order of magnitude higher means the common case — a
68
+ * timer landing inside one ordinary `UPDATE` — is served by the first retry
69
+ * instead of burning the whole budget on wakeups that all land inside the same
70
+ * statement.
71
+ *
72
+ * NOTE: 250 ms is reasoning, not measurement — nothing has measured how long a
73
+ * statement actually holds its implicit transaction, and a memory-backed one
74
+ * frequently never yields to the timer queue at all (a 400-row `insert … values`
75
+ * ran to completion without a single `setTimeout(0)` firing), so this mostly
76
+ * governs store-backed workloads and explicit `BEGIN`s. If the debug log shows
77
+ * crossings routinely spending the whole budget, measure a real workload before
78
+ * raising the retry count — a longer first delay is likelier to be the fix.
79
+ */
80
+ export const AUTO_ANALYZE_DEFER_RETRY_MS = 250;
81
+ /**
82
+ * How many times one crossing may reschedule itself after being deferred. The
83
+ * geometric backoff above makes that about 3.75 s of total patience.
84
+ *
85
+ * Bounded on purpose: a user may park an explicit transaction open indefinitely,
86
+ * and an unbounded retry would then cost a wakeup per stale table forever. Once
87
+ * the budget is spent the crossing is dropped exactly as it was before retries
88
+ * existed — the counter stays over threshold and the next commit that touches the
89
+ * table re-arms.
90
+ *
91
+ * Must stay at or below `AUTO_ANALYZE_IDLE_MAX_PASSES - 2`: {@link
92
+ * AutoAnalyzeManager.whenIdle} spends one settle pass per retry plus one for the
93
+ * initial attempt and one to observe that nothing is left armed.
94
+ */
95
+ export const AUTO_ANALYZE_MAX_DEFER_RETRIES = 4;
96
+ /**
97
+ * Safety bound on {@link AutoAnalyzeManager.whenIdle}'s settle loop. Exported so the
98
+ * inequality against {@link AUTO_ANALYZE_MAX_DEFER_RETRIES} is checked by a test
99
+ * rather than only implied by one failing if it were ever violated.
100
+ */
101
+ export const AUTO_ANALYZE_IDLE_MAX_PASSES = 10;
102
+ /**
103
+ * Delay for a debounce timer armed at `now` for a table not eligible until
104
+ * `nextEligibleAt`: the debounce, or the remaining duty-cycle cooldown when that
105
+ * is longer. Exported for the same reason {@link isStaleCount} is — the cooldown
106
+ * arithmetic is otherwise only observable through timing.
107
+ */
108
+ export function armDelayMs(nextEligibleAt, now) {
109
+ return Math.max(AUTO_ANALYZE_DEBOUNCE_MS, nextEligibleAt - now);
110
+ }
111
+ /**
112
+ * The staleness threshold in changed rows:
113
+ *
114
+ * ```
115
+ * stale ⟺ changedSinceAnalyze >= max(minMutations, ratio × knownRowCount)
116
+ * ```
117
+ *
118
+ * `knownRowCount` is 0 for a never-analyzed table (`SchemaManager` hardcodes
119
+ * `estimatedRows` to 0 at create), so the absolute floor governs exactly the
120
+ * case that matters most: bulk-loading rows into a fresh table trips
121
+ * `minMutations` long before any percentage of zero could.
122
+ *
123
+ * Precedent for the defaults: SQL Server's auto-update-stats uses 500 + 20% of
124
+ * rows; PostgreSQL's autoanalyze uses 50 + 10%.
125
+ */
126
+ export function stalenessThreshold(minMutations, ratio, knownRowCount) {
127
+ return Math.max(minMutations, ratio * knownRowCount);
128
+ }
129
+ /** Pure form of the staleness predicate — see {@link stalenessThreshold}. */
130
+ export function isStaleCount(changedSinceAnalyze, minMutations, ratio, knownRowCount) {
131
+ return changedSinceAnalyze >= stalenessThreshold(minMutations, ratio, knownRowCount);
132
+ }
133
+ /**
134
+ * Tracks committed-mutation drift per base table for a single `Database`, and
135
+ * schedules the background refresh that clears it.
136
+ */
137
+ export class AutoAnalyzeManager {
138
+ ctx;
139
+ /**
140
+ * Lowercased `schema.table` → staleness bookkeeping. Same key convention the
141
+ * change log uses, so no re-derivation and no second key convention.
142
+ *
143
+ * NOTE: nothing here is persisted. After a restart the store backend's saved
144
+ * statistics still exist, but staleness accumulation restarts from zero — a
145
+ * table that drifted while the process was down looks fresh until it drifts
146
+ * again. Accepted for v1 (a restart is rare relative to the mutation
147
+ * threshold). If it ever matters, persist `changedSinceAnalyze` beside the
148
+ * table's statistics entry and reload it when the catalog loads.
149
+ */
150
+ entries = new Map();
151
+ unsubscribeSchemaChanges = null;
152
+ /** Set by {@link dispose}; every scheduled path checks it before doing work. */
153
+ disposed = false;
154
+ /** Refreshes that actually reached the `ANALYZE` statement. Test instrumentation. */
155
+ refreshes = 0;
156
+ constructor(ctx) {
157
+ this.ctx = ctx;
158
+ this.subscribeToSchemaChanges();
159
+ }
160
+ subscribeToSchemaChanges() {
161
+ const notifier = this.ctx.schemaManager.getChangeNotifier();
162
+ this.unsubscribeSchemaChanges = notifier.addListener((event) => {
163
+ // Only `table_removed`, so a table re-created under a dropped name starts
164
+ // clean. Deliberately NOT `table_modified`: a statistics refresh fires that
165
+ // event itself (runtime/emit/analyze.ts), so reacting to it would couple this
166
+ // manager to its own output.
167
+ //
168
+ // That decoupling has a cost, taken knowingly: a hand-typed `analyze t` also
169
+ // arrives only as `table_modified`, so it does NOT reset `changedSinceAnalyze`.
170
+ // A user who analyzes an already-over-threshold table by hand leaves the
171
+ // counter where it was, and this manager then re-scans a table whose
172
+ // statistics are seconds old — one wasted O(rows) scan, after which the reset
173
+ // in `refresh` makes it self-correcting. The reset path keys off THIS
174
+ // MANAGER'S OWN refresh only. Distinguishing "a successful ANALYZE happened"
175
+ // from any other schema edit would need a signal the notifier does not carry;
176
+ // adding one to save a single redundant scan is not worth re-coupling to the
177
+ // channel the refresh itself fires.
178
+ //
179
+ // NOTE: `DETACH` removes a whole schema WITHOUT firing per-table events
180
+ // (`SchemaManager.removeSchema`), so entries for a detached schema's tables
181
+ // outlive it. Harmless today — each is a handful of numbers, and
182
+ // `knownRowCountOrDrop` evicts one the next time it is consulted — but if a
183
+ // host ever attaches and detaches schemas in a loop, drop entries whose
184
+ // schema segment matches the detached name.
185
+ if (event.type === 'table_removed') {
186
+ const key = `${event.schemaName}.${event.objectName}`.toLowerCase();
187
+ this.dropEntry(key);
188
+ }
189
+ });
190
+ }
191
+ /**
192
+ * Fold one committed transaction's per-table changed-row counts into the
193
+ * staleness map and re-evaluate the threshold for every table it touched.
194
+ * Entries are created lazily on first touch.
195
+ */
196
+ recordCommit(counts) {
197
+ for (const [key, count] of counts) {
198
+ if (count <= 0)
199
+ continue;
200
+ let entry = this.entries.get(key);
201
+ if (!entry) {
202
+ entry = {
203
+ changedSinceAnalyze: 0,
204
+ analyzedRowCount: undefined,
205
+ staleLogged: false,
206
+ timer: undefined,
207
+ running: undefined,
208
+ nextEligibleAt: 0,
209
+ oversizeLogged: false,
210
+ deferRetries: 0,
211
+ };
212
+ this.entries.set(key, entry);
213
+ }
214
+ entry.changedSinceAnalyze += count;
215
+ // A commit is proof the transaction that kept deferring this table's refresh
216
+ // has ended, so whatever retry budget it burned is refunded. Zeroed BEFORE
217
+ // `evaluate`, so the crossing this commit may arm starts with a full budget.
218
+ entry.deferRetries = 0;
219
+ this.evaluate(key, entry);
220
+ }
221
+ }
222
+ /**
223
+ * Whether the named table (lowercased `schema.table`) has drifted past the
224
+ * threshold. Not a pure predicate: a table that has since disappeared answers
225
+ * `false` and has its entry dropped — see {@link knownRowCountOrDrop}.
226
+ */
227
+ isStale(key) {
228
+ const entry = this.entries.get(key);
229
+ if (!entry)
230
+ return false;
231
+ const known = this.knownRowCountOrDrop(key, entry);
232
+ if (known === undefined)
233
+ return false;
234
+ return isStaleCount(entry.changedSinceAnalyze, this.minMutations(), this.ratio(), known);
235
+ }
236
+ /** @internal Current bookkeeping for a table; `undefined` when untracked. */
237
+ getEntry(key) {
238
+ return this.entries.get(key);
239
+ }
240
+ /** @internal Every table currently tracked (lowercased `schema.table`). */
241
+ trackedTables() {
242
+ return [...this.entries.keys()];
243
+ }
244
+ /**
245
+ * @internal How many refreshes have reached the `ANALYZE` statement since this
246
+ * manager was created. Lets a test assert coalescing by counting work rather than
247
+ * by timing it.
248
+ */
249
+ refreshCount() {
250
+ return this.refreshes;
251
+ }
252
+ /**
253
+ * @internal Resolve once no table has an armed timer or an in-flight refresh.
254
+ *
255
+ * Any armed timer is fired IMMEDIATELY — the debounce, the duty-cycle cooldown and
256
+ * the deferral backoff are all bypassed — rather than waited out, so tests never
257
+ * sleep.
258
+ *
259
+ * It deliberately does NOT bypass the open-transaction deferral: a test that wants
260
+ * a refresh to happen must be in autocommit, which is what real callers face.
261
+ *
262
+ * A retry timer is an armed timer like any other, so driving this while a
263
+ * transaction is open SPENDS the whole deferral budget in one call — fire, defer,
264
+ * re-arm, fire… until `AUTO_ANALYZE_MAX_DEFER_RETRIES` is reached, then settle.
265
+ * That is the intended behaviour, and it is what makes the budget observable
266
+ * without sleeping. It costs one settle pass per retry, which is why the budget
267
+ * must stay at or below `AUTO_ANALYZE_IDLE_MAX_PASSES - 2`.
268
+ */
269
+ async whenIdle() {
270
+ for (let pass = 0; pass < AUTO_ANALYZE_IDLE_MAX_PASSES; pass++) {
271
+ const pending = [];
272
+ for (const [key, entry] of [...this.entries]) {
273
+ if (entry.timer !== undefined) {
274
+ clearTimeout(entry.timer);
275
+ entry.timer = undefined;
276
+ entry.nextEligibleAt = 0;
277
+ this.start(key, entry);
278
+ }
279
+ if (entry.running !== undefined)
280
+ pending.push(entry.running);
281
+ }
282
+ if (pending.length === 0)
283
+ return;
284
+ await Promise.all(pending);
285
+ }
286
+ // Loud rather than hanging: a bounded self-re-arm (the deferral retry) is expected
287
+ // and fits inside the pass budget, so reaching here means an UNBOUNDED one.
288
+ warnLog('Auto-analyze did not settle after %d passes; still tracking: %s', AUTO_ANALYZE_IDLE_MAX_PASSES, [...this.entries.keys()].join(', '));
289
+ }
290
+ /**
291
+ * @internal Fire one table's armed timer NOW, bypassing its delay, and resolve once
292
+ * that SINGLE attempt has settled and its outcome has been applied. No-op when
293
+ * nothing is armed for `key`.
294
+ *
295
+ * {@link whenIdle} cannot stand in for this: it loops until nothing is armed, so
296
+ * driven while a transaction is open it spends the entire deferral budget in one
297
+ * call. A test that wants to watch one deferral reschedule itself needs one attempt.
298
+ */
299
+ async fireArmedRefresh(key) {
300
+ const entry = this.entries.get(key);
301
+ if (!entry || entry.timer === undefined)
302
+ return;
303
+ this.clearTimer(entry);
304
+ entry.nextEligibleAt = 0;
305
+ this.start(key, entry);
306
+ await entry.running;
307
+ }
308
+ dispose() {
309
+ this.disposed = true;
310
+ if (this.unsubscribeSchemaChanges) {
311
+ this.unsubscribeSchemaChanges();
312
+ this.unsubscribeSchemaChanges = null;
313
+ }
314
+ // Armed timers must not outlive the database — both to avoid holding a Node
315
+ // process open and to avoid a refresh firing against a closed database. An
316
+ // already-running refresh is left alone: `db.exec` throws once the database is
317
+ // closed, and the failure path logs and continues rather than rejecting.
318
+ for (const entry of this.entries.values()) {
319
+ this.clearTimer(entry);
320
+ }
321
+ this.entries.clear();
322
+ }
323
+ /** Cancel any armed timer for `entry`. Safe to call when none is armed. */
324
+ clearTimer(entry) {
325
+ if (entry.timer === undefined)
326
+ return;
327
+ clearTimeout(entry.timer);
328
+ entry.timer = undefined;
329
+ }
330
+ /** Forget a table, cancelling any refresh armed for it. */
331
+ dropEntry(key) {
332
+ const entry = this.entries.get(key);
333
+ if (!entry)
334
+ return;
335
+ this.clearTimer(entry);
336
+ this.entries.delete(key);
337
+ }
338
+ /**
339
+ * Row count the ratio arm of the threshold applies to: what the last refresh
340
+ * this process observed reported, else whatever the catalog knows.
341
+ *
342
+ * Named for its side effect: a table that is gone (dropped between the commit
343
+ * and this lookup, or carried in from a detached schema) answers `undefined`
344
+ * AND has its entry evicted here, so every consumer of a row count doubles as
345
+ * the eviction path for stale keys. That is why {@link isStale} mutates.
346
+ */
347
+ knownRowCountOrDrop(key, entry) {
348
+ if (entry.analyzedRowCount !== undefined)
349
+ return entry.analyzedRowCount;
350
+ const table = this.findTable(key);
351
+ if (!table) {
352
+ this.dropEntry(key);
353
+ return undefined;
354
+ }
355
+ return catalogRowCount(table) ?? 0;
356
+ }
357
+ findTable(key) {
358
+ const [schemaName, tableName] = splitBaseKey(key);
359
+ return this.ctx._findTable(tableName, schemaName);
360
+ }
361
+ /**
362
+ * Re-evaluate the threshold for one table after its counter moved: log the first
363
+ * crossing (a busy table must not flood the log) and arm a refresh.
364
+ */
365
+ evaluate(key, entry) {
366
+ // Absorbed: a pending refresh already covers whatever this commit added. The
367
+ // counter keeps climbing, and the NEXT commit after the refresh settles is what
368
+ // re-arms — nothing re-evaluates on the refresh's own completion. That is what
369
+ // makes N commits past the threshold cost O(1) refreshes rather than O(N), and
370
+ // it is checked first so the common busy-table case does no catalog lookup at
371
+ // all. Its cost: a burst that stops immediately after a refresh leaves a
372
+ // still-over-threshold counter with nothing scheduled until the next write.
373
+ if (entry.timer !== undefined || entry.running !== undefined)
374
+ return;
375
+ const known = this.knownRowCountOrDrop(key, entry);
376
+ if (known === undefined)
377
+ return;
378
+ const threshold = stalenessThreshold(this.minMutations(), this.ratio(), known);
379
+ if (entry.changedSinceAnalyze < threshold)
380
+ return;
381
+ if (!entry.staleLogged) {
382
+ entry.staleLogged = true;
383
+ debugLog('Statistics for %s are stale: %d rows changed since last analyze (threshold %d over %d known rows)', key, entry.changedSinceAnalyze, threshold, known);
384
+ }
385
+ this.arm(key, entry);
386
+ }
387
+ /** Schedule the debounced refresh for a table that has just crossed the threshold. */
388
+ arm(key, entry) {
389
+ if (this.disposed)
390
+ return;
391
+ if (entry.timer !== undefined || entry.running !== undefined)
392
+ return;
393
+ // A plain view stores no rows and `ANALYZE` rejects it by name, so never arm one.
394
+ // (A materialized view is a real backing table and is refreshed like any other.)
395
+ const table = this.findTable(key);
396
+ if (!table) {
397
+ this.dropEntry(key);
398
+ return;
399
+ }
400
+ if (table.isView)
401
+ return;
402
+ const delay = armDelayMs(entry.nextEligibleAt, Date.now());
403
+ const timer = setTimeout(() => {
404
+ entry.timer = undefined;
405
+ this.start(key, entry);
406
+ }, delay);
407
+ // `unref` is Node-only — browsers and React Native return a numeric handle with
408
+ // no such method. Where it exists, a pending refresh must not hold the process open.
409
+ const unref = timer.unref;
410
+ if (typeof unref === 'function')
411
+ unref.call(timer);
412
+ entry.timer = timer;
413
+ }
414
+ /**
415
+ * Begin a refresh, publish its settle promise on the entry, and act on how it
416
+ * ended. This is the single place that turns a {@link RefreshOutcome} into a
417
+ * scheduling decision — `refresh` itself only reports.
418
+ */
419
+ start(key, entry) {
420
+ if (entry.running !== undefined)
421
+ return;
422
+ // `running` must never reject: nothing awaits it in production, so a rejection
423
+ // from a timer callback is a process-level crash for a background chore. `refresh`
424
+ // has a total try/catch, and the trailing `catch` covers the rescheduling that
425
+ // follows it. Logged and dropped; the next commit re-arms.
426
+ const run = this.refresh(key, entry).then(outcome => {
427
+ // `running` must be cleared BEFORE the outcome is applied: `arm` early-returns
428
+ // while a refresh is in flight, so a retry armed ahead of this clear would be
429
+ // silently dropped. Sequenced, not raced.
430
+ if (entry.running === run)
431
+ entry.running = undefined;
432
+ this.applyOutcome(key, entry, outcome);
433
+ }).catch(e => {
434
+ warnLog('Auto-analyze rescheduling for %s failed: %s', key, e);
435
+ });
436
+ entry.running = run;
437
+ }
438
+ /** Decide what a finished refresh means for scheduling. */
439
+ applyOutcome(key, entry, outcome) {
440
+ switch (outcome) {
441
+ case 'analyzed':
442
+ // The crossing was served, so the next one starts with a full retry budget.
443
+ entry.deferRetries = 0;
444
+ break;
445
+ case 'deferred':
446
+ this.armDeferRetry(key, entry);
447
+ break;
448
+ case 'declined':
449
+ case 'failed':
450
+ // Nothing to reschedule. `declined` is a deliberate refusal that a retry would
451
+ // only repeat; `failed` already recorded its own backoff in `refresh`'s catch.
452
+ // Both wait for the next commit, as they did before retries existed.
453
+ break;
454
+ }
455
+ }
456
+ /**
457
+ * Reschedule a refusal that was purely about timing, on a geometric backoff and
458
+ * within a fixed budget. Once the budget is spent the crossing is dropped and the
459
+ * next commit that touches the table is what revives it.
460
+ */
461
+ armDeferRetry(key, entry) {
462
+ // The table may have been dropped, or its entry replaced, while the deferred
463
+ // refresh ran — do not resurrect a detached object. (`arm` re-checks the table
464
+ // itself; this checks the entry's identity, which `arm` cannot see.)
465
+ if (this.entries.get(key) !== entry)
466
+ return;
467
+ if (entry.deferRetries >= AUTO_ANALYZE_MAX_DEFER_RETRIES) {
468
+ debugLog('Auto-analyze of %s deferred %d times by an open transaction; dropping the crossing ' +
469
+ 'until the next commit touches the table', key, entry.deferRetries);
470
+ // Drop the backoff with it: the spent retries must not also delay the refresh
471
+ // the next commit arms. `deferRetries` itself stays at the cap until a commit
472
+ // or a successful refresh refunds it, so the spent budget stays observable.
473
+ //
474
+ // NOTE: zeroing is safe only because every refresh reaches here through `arm`,
475
+ // whose timer cannot fire before `nextEligibleAt` — so the duty-cycle cooldown
476
+ // this discards has already elapsed. If a future path ever starts a refresh
477
+ // without going through `arm`, this silently drops a live cooldown; carry the
478
+ // pre-deferral value instead of zeroing at that point.
479
+ entry.nextEligibleAt = 0;
480
+ return;
481
+ }
482
+ const delay = AUTO_ANALYZE_DEFER_RETRY_MS * 2 ** entry.deferRetries;
483
+ entry.deferRetries++;
484
+ // Expressed as a cooldown rather than a second timer concept, so `arm` and
485
+ // `armDelayMs` remain the only places a delay is computed.
486
+ entry.nextEligibleAt = Date.now() + delay;
487
+ this.arm(key, entry);
488
+ }
489
+ /**
490
+ * Refresh one table's statistics, reporting how it ended. Never throws: a failed
491
+ * automatic refresh must not surface as an error on an unrelated user statement,
492
+ * and nothing awaits this.
493
+ *
494
+ * It decides nothing about scheduling — every exit names a {@link RefreshOutcome}
495
+ * and {@link start} decides what that means. A new early return therefore has to
496
+ * say whether it is a refusal or a deferral, which is what keeps "abandoned
497
+ * crossing" from being writable by accident.
498
+ */
499
+ async refresh(key, entry) {
500
+ const startedAt = Date.now();
501
+ try {
502
+ if (this.disposed)
503
+ return 'declined';
504
+ // Re-read the switch at fire time: `pragma auto_analyze = false` between the
505
+ // arming and now must abandon the refresh, leaving the counter intact.
506
+ if (!this.enabled())
507
+ return 'declined';
508
+ // Deferred while a transaction is open. A memory table's `ANALYZE` adopts the
509
+ // connection already registered for the table INCLUDING its pending transaction
510
+ // layer (see `getStatistics` in `vtab/memory/table.ts`), so refreshing
511
+ // mid-transaction would bake uncommitted rows into the statistics.
512
+ //
513
+ // The counter is left untouched and the crossing is RETRIED on a backoff —
514
+ // `start` → `armDeferRetry`, up to AUTO_ANALYZE_MAX_DEFER_RETRIES times. That
515
+ // matters because this check is not only about an explicit `BEGIN`: a writing
516
+ // statement (`update`, `delete`, `insert … select`, any DDL) opens an implicit
517
+ // transaction too, so a timer for table `t` can land inside a write to some
518
+ // unrelated table. Without the retry that crossing was abandoned outright, and
519
+ // only the next commit touching `t` itself would ever revive it.
520
+ //
521
+ // NOTE: the retry budget is deliberately finite — see
522
+ // AUTO_ANALYZE_MAX_DEFER_RETRIES. A transaction held open past it drops the
523
+ // crossing and the pre-retry behaviour resumes: wait for the next commit.
524
+ //
525
+ // NOTE: accepted tradeoff — a `begin` landing between this check and `exec`'s
526
+ // mutex acquisition lets the refresh run inside that transaction after all.
527
+ // Worst outcome is statistics that include uncommitted rows, which is exactly
528
+ // what a user typing `begin; insert …; analyze;` gets today, and the store's
529
+ // `saveStatistics` already carries an accepted-tradeoff NOTE for the persisted
530
+ // half of the same situation. Closing it would mean holding the execution mutex
531
+ // across the check, i.e. blocking user statements on a background scan. Revisit
532
+ // only if statistics polluted by uncommitted rows show up in practice.
533
+ if (!this.ctx.getAutocommit())
534
+ return 'deferred';
535
+ // NOTE: a never-analyzed table reports 0 known rows (`SchemaManager` hardcodes
536
+ // `estimatedRows` to 0 at CREATE), so its FIRST automatic refresh is not size
537
+ // gated — a table bulk-loaded to 10M rows in a single transaction gets one
538
+ // unbounded scan before `analyzedRowCount` starts gating the rest. Deliberate:
539
+ // the obvious tightening — treating `changedSinceAnalyze` as a size proxy —
540
+ // deadlocks, because a table skipped as oversize never resets its counter and
541
+ // would therefore be skipped forever. If that first scan ever matters, give the
542
+ // gate a real size source (a module-reported row count) rather than a proxy.
543
+ const known = entry.analyzedRowCount ?? this.catalogRowCount(key) ?? 0;
544
+ const limit = this.rowLimit();
545
+ // 0 disables the cap (documented with the option), hence `limit > 0 &&` rather
546
+ // than a bare comparison — otherwise 0 would mean "never auto-analyze anything".
547
+ if (limit > 0 && known > limit) {
548
+ if (!entry.oversizeLogged) {
549
+ entry.oversizeLogged = true;
550
+ log('Statistics for %s are stale but the table is too large for auto-analyze ' +
551
+ '(%d known rows > auto_analyze_row_limit %d) — run ANALYZE manually', key, known, limit);
552
+ }
553
+ return 'declined';
554
+ }
555
+ const table = this.findTable(key);
556
+ if (!table || table.isView) {
557
+ this.dropEntry(key);
558
+ return 'declined';
559
+ }
560
+ // Snapshot rather than zero: mutations that commit between here and the reset
561
+ // below stay accounted for.
562
+ const snapshot = entry.changedSinceAnalyze;
563
+ this.refreshes++;
564
+ const [schemaName, tableName] = splitBaseKey(key);
565
+ await this.ctx.exec(`analyze ${quoteIdentifier(schemaName)}.${quoteIdentifier(tableName)}`);
566
+ // The table may have been dropped while the refresh ran, in which case
567
+ // `dropEntry` already discarded this object — do not resurrect it.
568
+ if (this.entries.get(key) !== entry)
569
+ return 'declined';
570
+ entry.changedSinceAnalyze = Math.max(0, entry.changedSinceAnalyze - snapshot);
571
+ const refreshed = this.findTable(key);
572
+ entry.analyzedRowCount = refreshed ? catalogRowCount(refreshed) : undefined;
573
+ entry.staleLogged = false;
574
+ entry.oversizeLogged = false;
575
+ const elapsed = Date.now() - startedAt;
576
+ entry.nextEligibleAt = Date.now() + elapsed * AUTO_ANALYZE_DUTY_CYCLE;
577
+ debugLog('Auto-analyzed %s in %d ms (%s rows)', key, elapsed, entry.analyzedRowCount);
578
+ return 'analyzed';
579
+ }
580
+ catch (e) {
581
+ // The staleness is real, so the counter is left alone and the next commit
582
+ // re-arms — behind a backoff, so an unreadable table cannot spin.
583
+ warnLog('Automatic ANALYZE of %s failed: %s', key, e);
584
+ entry.nextEligibleAt = Date.now() + Math.max(AUTO_ANALYZE_FAILURE_BACKOFF_MS, (Date.now() - startedAt) * AUTO_ANALYZE_DUTY_CYCLE);
585
+ return 'failed';
586
+ }
587
+ }
588
+ /** Catalog row count for a table, ignoring the entry's own record. */
589
+ catalogRowCount(key) {
590
+ const table = this.findTable(key);
591
+ return table ? catalogRowCount(table) : undefined;
592
+ }
593
+ enabled() {
594
+ return this.ctx.options.getBooleanOption('auto_analyze');
595
+ }
596
+ minMutations() {
597
+ return this.ctx.options.getNumberOption('auto_analyze_min_mutations');
598
+ }
599
+ ratio() {
600
+ return this.ctx.options.getNumberOption('auto_analyze_ratio');
601
+ }
602
+ /**
603
+ * Largest table (in known rows) an automatic refresh will scan; 0 disables the cap.
604
+ *
605
+ * NOTE: the gate is purely on row count. An earlier design also exempted modules
606
+ * whose `getStatistics()` answers the whole question cheaply, but no shipped module
607
+ * does — both the store backend (`store-table-base.ts`) and the memory backend
608
+ * (`vtab/memory/table.ts`) deliberately return an EMPTY `columnStats`, which
609
+ * `ANALYZE` reads as "size answered, scan for the rest". The exemption would be dead
610
+ * code. If a module that reports complete statistics ever appears, exempt it here.
611
+ */
612
+ rowLimit() {
613
+ return this.ctx.options.getNumberOption('auto_analyze_row_limit');
614
+ }
615
+ }
616
+ //# sourceMappingURL=database-auto-analyze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-auto-analyze.js","sourceRoot":"","sources":["../../../src/core/database-auto-analyze.ts"],"names":[],"mappings":"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"}