@quereus/quereus 4.21.1 → 4.22.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 (59) hide show
  1. package/dist/src/core/database-cardinality-feedback.d.ts +146 -0
  2. package/dist/src/core/database-cardinality-feedback.d.ts.map +1 -0
  3. package/dist/src/core/database-cardinality-feedback.js +274 -0
  4. package/dist/src/core/database-cardinality-feedback.js.map +1 -0
  5. package/dist/src/core/database-transaction.d.ts +9 -2
  6. package/dist/src/core/database-transaction.d.ts.map +1 -1
  7. package/dist/src/core/database-transaction.js +27 -9
  8. package/dist/src/core/database-transaction.js.map +1 -1
  9. package/dist/src/core/database.d.ts +4 -0
  10. package/dist/src/core/database.d.ts.map +1 -1
  11. package/dist/src/core/database.js +78 -7
  12. package/dist/src/core/database.js.map +1 -1
  13. package/dist/src/core/statement.d.ts +11 -2
  14. package/dist/src/core/statement.d.ts.map +1 -1
  15. package/dist/src/core/statement.js +46 -2
  16. package/dist/src/core/statement.js.map +1 -1
  17. package/dist/src/func/builtins/cardinality-feedback.d.ts +9 -0
  18. package/dist/src/func/builtins/cardinality-feedback.d.ts.map +1 -0
  19. package/dist/src/func/builtins/cardinality-feedback.js +54 -0
  20. package/dist/src/func/builtins/cardinality-feedback.js.map +1 -0
  21. package/dist/src/func/builtins/index.d.ts.map +1 -1
  22. package/dist/src/func/builtins/index.js +2 -0
  23. package/dist/src/func/builtins/index.js.map +1 -1
  24. package/dist/src/planner/nodes/reference.d.ts +7 -0
  25. package/dist/src/planner/nodes/reference.d.ts.map +1 -1
  26. package/dist/src/planner/nodes/reference.js +10 -2
  27. package/dist/src/planner/nodes/reference.js.map +1 -1
  28. package/dist/src/planner/optimizer-tuning.d.ts +29 -0
  29. package/dist/src/planner/optimizer-tuning.d.ts.map +1 -1
  30. package/dist/src/planner/optimizer-tuning.js +7 -1
  31. package/dist/src/planner/optimizer-tuning.js.map +1 -1
  32. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +6 -6
  33. package/dist/src/planner/stats/catalog-stats.d.ts +11 -2
  34. package/dist/src/planner/stats/catalog-stats.d.ts.map +1 -1
  35. package/dist/src/planner/stats/catalog-stats.js +14 -5
  36. package/dist/src/planner/stats/catalog-stats.js.map +1 -1
  37. package/dist/src/planner/stats/table-cardinality.d.ts +52 -18
  38. package/dist/src/planner/stats/table-cardinality.d.ts.map +1 -1
  39. package/dist/src/planner/stats/table-cardinality.js +46 -18
  40. package/dist/src/planner/stats/table-cardinality.js.map +1 -1
  41. package/dist/src/runtime/cardinality-observations.d.ts +30 -0
  42. package/dist/src/runtime/cardinality-observations.d.ts.map +1 -0
  43. package/dist/src/runtime/cardinality-observations.js +37 -0
  44. package/dist/src/runtime/cardinality-observations.js.map +1 -0
  45. package/dist/src/runtime/emit/scan.d.ts.map +1 -1
  46. package/dist/src/runtime/emit/scan.js +29 -0
  47. package/dist/src/runtime/emit/scan.js.map +1 -1
  48. package/dist/src/runtime/parallel-driver.d.ts +3 -0
  49. package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
  50. package/dist/src/runtime/parallel-driver.js +4 -0
  51. package/dist/src/runtime/parallel-driver.js.map +1 -1
  52. package/dist/src/runtime/types.d.ts +12 -0
  53. package/dist/src/runtime/types.d.ts.map +1 -1
  54. package/dist/src/runtime/types.js.map +1 -1
  55. package/dist/src/schema/manager.d.ts +7 -0
  56. package/dist/src/schema/manager.d.ts.map +1 -1
  57. package/dist/src/schema/manager.js +8 -0
  58. package/dist/src/schema/manager.js.map +1 -1
  59. package/package.json +3 -3
@@ -0,0 +1,146 @@
1
+ /**
2
+ * The observed row-count overlay: exact base-table sizes seen during execution.
3
+ *
4
+ * One per `Database`. The planner reads it through `plannedRowCount`
5
+ * (`planner/stats/table-cardinality.ts`), where an answering entry outranks the
6
+ * catalog's `ANALYZE` count. This module owns the record policy — which
7
+ * observations are worth keeping, and when one has moved far enough from what
8
+ * plans were costed against to call it a *shift* — and the entries' lifetime.
9
+ *
10
+ * Validity is schema identity: an entry answers only for the exact `TableSchema`
11
+ * object it was observed under. Any swap (`ANALYZE`, `ALTER TABLE`, drop and
12
+ * re-create) silences it, and a schema-change listener evicts it. The identity
13
+ * check is the correctness guard; the listener is memory hygiene.
14
+ *
15
+ * Execution feeds it through {@link CardinalityFeedbackManager.publish}, which admits
16
+ * only counts of committed state: a count taken inside a transaction is held until
17
+ * that transaction commits ({@link CardinalityFeedbackManager.onCommitted}) and
18
+ * dropped if it rolls back ({@link CardinalityFeedbackManager.onRolledBack}).
19
+ *
20
+ * A shift reaches prepared statements through `onShift`; each statement paces its
21
+ * re-plans with a {@link FeedbackReplanState}.
22
+ *
23
+ * Nothing here is persisted: a new `Database` starts empty. See
24
+ * docs/progressive-optimizer.md §3.2–3.6.
25
+ */
26
+ import type { OptimizerTuning } from '../planner/optimizer-tuning.js';
27
+ import { CardinalityObservations } from '../runtime/cardinality-observations.js';
28
+ import { type ObservedRowCounts } from '../planner/stats/table-cardinality.js';
29
+ import type { TableSchema } from '../schema/table.js';
30
+ import type { Database } from './database.js';
31
+ /** Database internals the manager needs — keeps it constructible without the full `Database`. */
32
+ export interface CardinalityFeedbackManagerContext {
33
+ readonly schemaManager: Database['schemaManager'];
34
+ /** Read on every record, so `Database.updateOptimizerTuning` takes effect immediately. */
35
+ getTuning(): OptimizerTuning;
36
+ /** True when no transaction (explicit or implicit) is open. */
37
+ getAutocommit(): boolean;
38
+ /**
39
+ * A recorded count moved at least `cardinalityFeedback.shiftFactor` away from the
40
+ * entry's baseline: plans costed against the old number may be wrong. `key` is the
41
+ * lowercased `schema.table`.
42
+ */
43
+ onShift(key: string): void;
44
+ }
45
+ /** By-value view of one valid entry, for tests and diagnostics. */
46
+ export interface ObservedRowCountSnapshot {
47
+ readonly schemaName: string;
48
+ readonly tableName: string;
49
+ readonly rows: number;
50
+ /** What the catalog (`ANALYZE`) says for the same table; `undefined` when never analyzed. */
51
+ readonly catalogRows: number | undefined;
52
+ readonly baselineRows: number;
53
+ readonly observations: number;
54
+ readonly shifts: number;
55
+ }
56
+ /** The overlay's key for a table: lowercased `schema.table`. Shifts are reported under it. */
57
+ export declare function overlayTableKey(schemaName: string, tableName: string): string;
58
+ /**
59
+ * One prepared statement's feedback re-plan cooldown (docs/progressive-optimizer.md §3.6).
60
+ * A shift marks the cached plan; the mark is acted on at an execution start, never
61
+ * mid-execution. The first feedback re-plan is immediate; re-plan `k + 1` waits until the
62
+ * statement has executed `cooldown × 2^min(k − 1, 6)` times since re-plan `k` — a mark
63
+ * arriving sooner stays pending until then.
64
+ */
65
+ export declare class FeedbackReplanState {
66
+ private marked;
67
+ /** Feedback re-plans so far. */
68
+ private replans;
69
+ /** Executions started since the last feedback re-plan, the re-planning one included. */
70
+ private executionsSinceReplan;
71
+ /** A table the cached plan reads shifted. */
72
+ markShift(): void;
73
+ /** The cached plan was dropped for any reason; whatever compiles next reads the current overlay. */
74
+ onPlanDropped(): void;
75
+ /** Counts one execution; true when the marked plan must be dropped before it runs. */
76
+ onExecutionStart(cooldown: number): boolean;
77
+ private requiredExecutions;
78
+ }
79
+ export declare class CardinalityFeedbackManager implements ObservedRowCounts {
80
+ private readonly ctx;
81
+ /**
82
+ * Lowercased `schema.table` → entry, the change-log / work-counter key convention.
83
+ * `Map` insertion order doubles as recency: a hit or a record re-inserts, so the
84
+ * first key is the least recently used.
85
+ */
86
+ private readonly entries;
87
+ /**
88
+ * Observations made inside the open transaction, held until it ends; same key as
89
+ * `entries`, last count per table wins. Bounded by the distinct tables one
90
+ * transaction observes.
91
+ */
92
+ private readonly pending;
93
+ /** {@link onRolledBack} calls so far; each execution's sink records it at start. */
94
+ private rollbacks;
95
+ private unsubscribeSchemaChanges;
96
+ constructor(ctx: CardinalityFeedbackManagerContext);
97
+ observedRows(table: TableSchema): number | undefined;
98
+ /**
99
+ * Offer one exact row count of `table`, observed by a complete enumeration of
100
+ * committed state. Applies the record policy (docs/progressive-optimizer.md §3.4):
101
+ * ignored when the schema object is superseded or when both the observation and the
102
+ * current estimate are small; otherwise recorded, and reported through `onShift`
103
+ * when it has moved `shiftFactor` from the entry's baseline.
104
+ */
105
+ record(table: TableSchema, rows: number): void;
106
+ /**
107
+ * Offer one finished execution's observations (docs/progressive-optimizer.md §3.5).
108
+ * A committed read (`readCommitted`) or an execution that ended with no transaction
109
+ * open saw committed state and is recorded now. Anything else may include the open
110
+ * transaction's uncommitted writes, so it is held for {@link onCommitted} — unless a
111
+ * rollback ran during the execution (typically a failing statement's own statement
112
+ * savepoint, which fires before publication): then a completed scan may have counted
113
+ * rows that no longer exist, and the whole execution is discarded.
114
+ */
115
+ publish(observations: CardinalityObservations, options: {
116
+ readCommitted: boolean;
117
+ }): void;
118
+ /** A fresh per-execution sink, stamped so {@link publish} can tell whether a rollback ran under it. */
119
+ beginObservations(): CardinalityObservations;
120
+ /**
121
+ * The open transaction committed: record what it held, except tables it wrote.
122
+ * Such a count was taken mid-transaction, possibly before later writes in the same
123
+ * transaction, and the change log cannot say which came first. `changedCounts` is
124
+ * the change log keyed like `entries` — every table the transaction wrote, even one
125
+ * whose writes cancelled out — and is not built when nothing is held.
126
+ */
127
+ onCommitted(changedCounts: () => Map<string, number>): void;
128
+ /** The open transaction, or some of its work, was undone: forget everything held. */
129
+ onRolledBack(): void;
130
+ /** Forget every observation, held ones included; later compiles read the catalog only. */
131
+ clear(): void;
132
+ /** Valid entries, by value, least recently used first. Does not refresh recency. */
133
+ snapshot(): ObservedRowCountSnapshot[];
134
+ dispose(): void;
135
+ private subscribeToSchemaChanges;
136
+ private recordAll;
137
+ /** The entry under `key` if it was observed against `table`; a stale one is replaced on record. */
138
+ private validEntry;
139
+ private touch;
140
+ /**
141
+ * Drop least-recently-used entries past the cap. No `onShift`: plans already built
142
+ * keep their snapshot, and the next compile falls back to catalog or default numbers.
143
+ */
144
+ private evictBeyond;
145
+ }
146
+ //# sourceMappingURL=database-cardinality-feedback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-cardinality-feedback.d.ts","sourceRoot":"","sources":["../../../src/core/database-cardinality-feedback.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,274 @@
1
+ /**
2
+ * The observed row-count overlay: exact base-table sizes seen during execution.
3
+ *
4
+ * One per `Database`. The planner reads it through `plannedRowCount`
5
+ * (`planner/stats/table-cardinality.ts`), where an answering entry outranks the
6
+ * catalog's `ANALYZE` count. This module owns the record policy — which
7
+ * observations are worth keeping, and when one has moved far enough from what
8
+ * plans were costed against to call it a *shift* — and the entries' lifetime.
9
+ *
10
+ * Validity is schema identity: an entry answers only for the exact `TableSchema`
11
+ * object it was observed under. Any swap (`ANALYZE`, `ALTER TABLE`, drop and
12
+ * re-create) silences it, and a schema-change listener evicts it. The identity
13
+ * check is the correctness guard; the listener is memory hygiene.
14
+ *
15
+ * Execution feeds it through {@link CardinalityFeedbackManager.publish}, which admits
16
+ * only counts of committed state: a count taken inside a transaction is held until
17
+ * that transaction commits ({@link CardinalityFeedbackManager.onCommitted}) and
18
+ * dropped if it rolls back ({@link CardinalityFeedbackManager.onRolledBack}).
19
+ *
20
+ * A shift reaches prepared statements through `onShift`; each statement paces its
21
+ * re-plans with a {@link FeedbackReplanState}.
22
+ *
23
+ * Nothing here is persisted: a new `Database` starts empty. See
24
+ * docs/progressive-optimizer.md §3.2–3.6.
25
+ */
26
+ import { createLogger } from '../common/logger.js';
27
+ import { CardinalityObservations } from '../runtime/cardinality-observations.js';
28
+ import { catalogRowCount } from '../planner/stats/table-cardinality.js';
29
+ const log = createLogger('core:cardinality-feedback');
30
+ const debugLog = log.extend('debug');
31
+ /**
32
+ * Distance between two counts as a ratio ≥ 1. The `+ 1` on both sides keeps a zero
33
+ * count finite and makes tiny counts compare sanely (0 vs 3 is 4×, not infinite).
34
+ */
35
+ function rowCountSpread(a, b) {
36
+ return (Math.max(a, b) + 1) / (Math.min(a, b) + 1);
37
+ }
38
+ /** The overlay's key for a table: lowercased `schema.table`. Shifts are reported under it. */
39
+ export function overlayTableKey(schemaName, tableName) {
40
+ return `${schemaName}.${tableName}`.toLowerCase();
41
+ }
42
+ function tableKey(table) {
43
+ return overlayTableKey(table.schemaName, table.name);
44
+ }
45
+ /** Cap on the cooldown's doubling: re-plans past the seventh wait `64 × replanCooldown` executions. */
46
+ const MAX_COOLDOWN_DOUBLINGS = 6;
47
+ /**
48
+ * One prepared statement's feedback re-plan cooldown (docs/progressive-optimizer.md §3.6).
49
+ * A shift marks the cached plan; the mark is acted on at an execution start, never
50
+ * mid-execution. The first feedback re-plan is immediate; re-plan `k + 1` waits until the
51
+ * statement has executed `cooldown × 2^min(k − 1, 6)` times since re-plan `k` — a mark
52
+ * arriving sooner stays pending until then.
53
+ */
54
+ export class FeedbackReplanState {
55
+ marked = false;
56
+ /** Feedback re-plans so far. */
57
+ replans = 0;
58
+ /** Executions started since the last feedback re-plan, the re-planning one included. */
59
+ executionsSinceReplan = 0;
60
+ /** A table the cached plan reads shifted. */
61
+ markShift() {
62
+ this.marked = true;
63
+ }
64
+ /** The cached plan was dropped for any reason; whatever compiles next reads the current overlay. */
65
+ onPlanDropped() {
66
+ this.marked = false;
67
+ }
68
+ /** Counts one execution; true when the marked plan must be dropped before it runs. */
69
+ onExecutionStart(cooldown) {
70
+ const due = this.marked && (this.replans === 0 || this.executionsSinceReplan >= this.requiredExecutions(cooldown));
71
+ if (due) {
72
+ this.marked = false;
73
+ this.replans++;
74
+ this.executionsSinceReplan = 0;
75
+ }
76
+ this.executionsSinceReplan++;
77
+ return due;
78
+ }
79
+ requiredExecutions(cooldown) {
80
+ return cooldown * 2 ** Math.min(this.replans - 1, MAX_COOLDOWN_DOUBLINGS);
81
+ }
82
+ }
83
+ export class CardinalityFeedbackManager {
84
+ ctx;
85
+ /**
86
+ * Lowercased `schema.table` → entry, the change-log / work-counter key convention.
87
+ * `Map` insertion order doubles as recency: a hit or a record re-inserts, so the
88
+ * first key is the least recently used.
89
+ */
90
+ entries = new Map();
91
+ /**
92
+ * Observations made inside the open transaction, held until it ends; same key as
93
+ * `entries`, last count per table wins. Bounded by the distinct tables one
94
+ * transaction observes.
95
+ */
96
+ pending = new Map();
97
+ /** {@link onRolledBack} calls so far; each execution's sink records it at start. */
98
+ rollbacks = 0;
99
+ unsubscribeSchemaChanges = null;
100
+ constructor(ctx) {
101
+ this.ctx = ctx;
102
+ this.subscribeToSchemaChanges();
103
+ }
104
+ observedRows(table) {
105
+ const key = tableKey(table);
106
+ const entry = this.entries.get(key);
107
+ if (!entry || entry.schema !== table)
108
+ return undefined;
109
+ this.touch(key, entry);
110
+ return entry.rows;
111
+ }
112
+ /**
113
+ * Offer one exact row count of `table`, observed by a complete enumeration of
114
+ * committed state. Applies the record policy (docs/progressive-optimizer.md §3.4):
115
+ * ignored when the schema object is superseded or when both the observation and the
116
+ * current estimate are small; otherwise recorded, and reported through `onShift`
117
+ * when it has moved `shiftFactor` from the entry's baseline.
118
+ */
119
+ record(table, rows) {
120
+ if (this.ctx.schemaManager.findTable(table.name, table.schemaName) !== table) {
121
+ debugLog('Ignored %d rows for %s.%s: observed under a superseded schema', rows, table.schemaName, table.name);
122
+ return;
123
+ }
124
+ const tuning = this.ctx.getTuning();
125
+ const policy = tuning.cardinalityFeedback;
126
+ const key = tableKey(table);
127
+ const existing = this.validEntry(key, table);
128
+ const before = existing?.rows ?? catalogRowCount(table) ?? tuning.defaultRowEstimate;
129
+ if (Math.max(before, rows) <= policy.minRows)
130
+ return;
131
+ const entry = existing ?? { schema: table, rows, baselineRows: before, observations: 0, shifts: 0 };
132
+ entry.rows = rows;
133
+ entry.observations++;
134
+ this.touch(key, entry);
135
+ this.evictBeyond(policy.maxTables);
136
+ const shifted = rowCountSpread(rows, entry.baselineRows) >= policy.shiftFactor;
137
+ debugLog('Recorded %d rows for %s (was %d, baseline %d)%s', rows, key, before, entry.baselineRows, shifted ? ' — shift' : '');
138
+ if (!shifted)
139
+ return;
140
+ log('Shift on %s: baseline %d -> %d rows', key, entry.baselineRows, rows);
141
+ entry.baselineRows = rows;
142
+ entry.shifts++;
143
+ this.ctx.onShift(key);
144
+ }
145
+ /**
146
+ * Offer one finished execution's observations (docs/progressive-optimizer.md §3.5).
147
+ * A committed read (`readCommitted`) or an execution that ended with no transaction
148
+ * open saw committed state and is recorded now. Anything else may include the open
149
+ * transaction's uncommitted writes, so it is held for {@link onCommitted} — unless a
150
+ * rollback ran during the execution (typically a failing statement's own statement
151
+ * savepoint, which fires before publication): then a completed scan may have counted
152
+ * rows that no longer exist, and the whole execution is discarded.
153
+ */
154
+ publish(observations, options) {
155
+ if (options.readCommitted) {
156
+ this.recordAll(observations);
157
+ }
158
+ else if (observations.rollbacksAtStart !== this.rollbacks) {
159
+ debugLog('Discarded observations: work was rolled back during the execution');
160
+ }
161
+ else if (this.ctx.getAutocommit()) {
162
+ this.recordAll(observations);
163
+ }
164
+ else {
165
+ for (const [table, rows] of observations.entries()) {
166
+ this.pending.set(tableKey(table), { table, rows });
167
+ }
168
+ }
169
+ }
170
+ /** A fresh per-execution sink, stamped so {@link publish} can tell whether a rollback ran under it. */
171
+ beginObservations() {
172
+ return new CardinalityObservations(this.rollbacks);
173
+ }
174
+ /**
175
+ * The open transaction committed: record what it held, except tables it wrote.
176
+ * Such a count was taken mid-transaction, possibly before later writes in the same
177
+ * transaction, and the change log cannot say which came first. `changedCounts` is
178
+ * the change log keyed like `entries` — every table the transaction wrote, even one
179
+ * whose writes cancelled out — and is not built when nothing is held.
180
+ */
181
+ onCommitted(changedCounts) {
182
+ // NOTE: external-change ingestion with `captureChanges: false` writes rows the change
183
+ // log never sees, so a count held from before such a batch is applied. Only an estimate
184
+ // lags; if those batches ever routinely shrink or grow tables, have them veto here too.
185
+ if (this.pending.size === 0)
186
+ return;
187
+ try {
188
+ const changed = changedCounts();
189
+ for (const [key, { table, rows }] of this.pending) {
190
+ if (changed.has(key)) {
191
+ debugLog('Dropped %d rows for %s: the committing transaction wrote it', rows, key);
192
+ continue;
193
+ }
194
+ this.record(table, rows);
195
+ }
196
+ }
197
+ finally {
198
+ this.pending.clear();
199
+ }
200
+ }
201
+ /** The open transaction, or some of its work, was undone: forget everything held. */
202
+ onRolledBack() {
203
+ this.pending.clear();
204
+ this.rollbacks++;
205
+ }
206
+ /** Forget every observation, held ones included; later compiles read the catalog only. */
207
+ clear() {
208
+ this.entries.clear();
209
+ this.pending.clear();
210
+ }
211
+ /** Valid entries, by value, least recently used first. Does not refresh recency. */
212
+ snapshot() {
213
+ const out = [];
214
+ for (const entry of this.entries.values()) {
215
+ const { schema } = entry;
216
+ if (this.ctx.schemaManager.findTable(schema.name, schema.schemaName) !== schema)
217
+ continue;
218
+ out.push({
219
+ schemaName: schema.schemaName,
220
+ tableName: schema.name,
221
+ rows: entry.rows,
222
+ catalogRows: catalogRowCount(schema),
223
+ baselineRows: entry.baselineRows,
224
+ observations: entry.observations,
225
+ shifts: entry.shifts,
226
+ });
227
+ }
228
+ return out;
229
+ }
230
+ dispose() {
231
+ if (this.unsubscribeSchemaChanges) {
232
+ this.unsubscribeSchemaChanges();
233
+ this.unsubscribeSchemaChanges = null;
234
+ }
235
+ this.clear();
236
+ }
237
+ subscribeToSchemaChanges() {
238
+ const notifier = this.ctx.schemaManager.getChangeNotifier();
239
+ this.unsubscribeSchemaChanges = notifier.addListener((event) => {
240
+ // Keyed by `oldObject`, not the event's name: a rename reports the NEW name.
241
+ // `DETACH` fires no per-table events; its tables' entries never match again
242
+ // and age out under the LRU cap.
243
+ if (event.type === 'table_modified' || event.type === 'table_removed') {
244
+ this.entries.delete(tableKey(event.oldObject));
245
+ }
246
+ });
247
+ }
248
+ recordAll(observations) {
249
+ for (const [table, rows] of observations.entries()) {
250
+ this.record(table, rows);
251
+ }
252
+ }
253
+ /** The entry under `key` if it was observed against `table`; a stale one is replaced on record. */
254
+ validEntry(key, table) {
255
+ const entry = this.entries.get(key);
256
+ return entry?.schema === table ? entry : undefined;
257
+ }
258
+ touch(key, entry) {
259
+ this.entries.delete(key);
260
+ this.entries.set(key, entry);
261
+ }
262
+ /**
263
+ * Drop least-recently-used entries past the cap. No `onShift`: plans already built
264
+ * keep their snapshot, and the next compile falls back to catalog or default numbers.
265
+ */
266
+ evictBeyond(maxTables) {
267
+ for (const key of this.entries.keys()) {
268
+ if (this.entries.size <= maxTables)
269
+ return;
270
+ this.entries.delete(key);
271
+ }
272
+ }
273
+ }
274
+ //# sourceMappingURL=database-cardinality-feedback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-cardinality-feedback.js","sourceRoot":"","sources":["../../../src/core/database-cardinality-feedback.ts"],"names":[],"mappings":"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"}
@@ -41,6 +41,9 @@ export interface TransactionManagerContext {
41
41
  /** Called after a successful commit, before the change log is cleared. The callback
42
42
  * materializes per-table committed row counts only if a consumer wants them. */
43
43
  recordCommittedChangeCounts(counts: () => Map<string, number>): void;
44
+ /** Called when work of the open transaction is undone: a rollback, a failed commit,
45
+ * or a `rollback to savepoint`. Bookkeeping only — errors are logged, never thrown. */
46
+ transactionRolledBack(): void;
44
47
  }
45
48
  /**
46
49
  * Op kind for a captured row change.
@@ -126,6 +129,8 @@ export declare class TransactionManager {
126
129
  * Rolls back the current transaction on all connections.
127
130
  */
128
131
  rollbackTransaction(): Promise<void>;
132
+ /** Tell the context work was undone, isolated like commit bookkeeping. */
133
+ private notifyRolledBack;
129
134
  /**
130
135
  * Ensures we're in a transaction. If in autocommit mode, starts an implicit transaction.
131
136
  */
@@ -220,8 +225,10 @@ export declare class TransactionManager {
220
225
  * so the whole reading is O(tables × layers) with no scan.
221
226
  *
222
227
  * Inherits the change log's coalescing — ten updates to one row count as one,
223
- * and an insert followed by a delete of the same key leaves no entry at all.
224
- * That makes this a *distinct rows touched* measure.
228
+ * and an insert followed by a delete of the same key counts zero. That makes
229
+ * this a *distinct rows touched* measure. A table written whose changes all
230
+ * cancelled out still appears, with count 0: the key set is every table the
231
+ * transaction wrote, which held cardinality observations rely on.
225
232
  *
226
233
  * Deliberately approximate across layers: the same key changed in two live
227
234
  * savepoint layers is counted twice. Deduplicating would cost a scan of every
@@ -1 +1 @@
1
- {"version":3,"file":"database-transaction.d.ts","sourceRoot":"","sources":["../../../src/core/database-transaction.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"database-transaction.d.ts","sourceRoot":"","sources":["../../../src/core/database-transaction.ts"],"names":[],"mappings":"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"}
@@ -107,6 +107,11 @@ export class TransactionManager {
107
107
  // Upgrade implicit to explicit
108
108
  debugLog('Upgrading implicit transaction to explicit (BEGIN encountered).');
109
109
  this.transactionSource = 'explicit';
110
+ // NOTE: held cardinality observations survive the upgrade (same transaction),
111
+ // but this clear forgets which tables were written before it, so a pre-upgrade
112
+ // write after an observation would no longer veto that observation at commit.
113
+ // Unreached today — every implicit transaction ends with its statement — if
114
+ // BEGIN can ever run inside one that wrote, keep the written-table set here.
110
115
  this.clearChangeLog();
111
116
  return;
112
117
  }
@@ -189,15 +194,16 @@ export class TransactionManager {
189
194
  catch (err) {
190
195
  errorLog('Post-commit watcher dispatch threw: %O', err);
191
196
  }
192
- // Auto-analyze staleness bookkeeping, same window: the change log is
193
- // still alive. The counts map is thunked so it is never materialized
194
- // when the feature is off, and the whole call is isolated — a
195
- // bookkeeping error must never fail a transaction that has committed.
197
+ // Commit bookkeeping (auto-analyze staleness, held cardinality
198
+ // observations), same window: the change log is still alive. The counts
199
+ // map is thunked so it is never materialized when no consumer wants it,
200
+ // and the whole call is isolated — a bookkeeping error must never fail a
201
+ // transaction that has committed.
196
202
  try {
197
203
  this.ctx.recordCommittedChangeCounts(() => this.getChangedRowCounts());
198
204
  }
199
205
  catch (err) {
200
- errorLog('Auto-analyze commit bookkeeping threw: %O', err);
206
+ errorLog('Commit bookkeeping threw: %O', err);
201
207
  }
202
208
  // Materialized views are NOT a post-commit consumer: each is row-time
203
209
  // maintained synchronously at the DML boundary, so its backing table is
@@ -207,6 +213,7 @@ export class TransactionManager {
207
213
  // On pre-commit assertion failure (or commit error), rollback all connections
208
214
  const conns = this.ctx.getAllConnections();
209
215
  await Promise.allSettled(conns.map(c => c.rollback()));
216
+ this.notifyRolledBack();
210
217
  throw e;
211
218
  }
212
219
  finally {
@@ -257,6 +264,16 @@ export class TransactionManager {
257
264
  this.isAutocommit = true;
258
265
  this.transactionSource = null;
259
266
  this.clearChangeLog();
267
+ this.notifyRolledBack();
268
+ }
269
+ }
270
+ /** Tell the context work was undone, isolated like commit bookkeeping. */
271
+ notifyRolledBack() {
272
+ try {
273
+ this.ctx.transactionRolledBack();
274
+ }
275
+ catch (err) {
276
+ errorLog('Rollback bookkeeping threw: %O', err);
260
277
  }
261
278
  }
262
279
  /**
@@ -542,8 +559,10 @@ export class TransactionManager {
542
559
  * so the whole reading is O(tables × layers) with no scan.
543
560
  *
544
561
  * Inherits the change log's coalescing — ten updates to one row count as one,
545
- * and an insert followed by a delete of the same key leaves no entry at all.
546
- * That makes this a *distinct rows touched* measure.
562
+ * and an insert followed by a delete of the same key counts zero. That makes
563
+ * this a *distinct rows touched* measure. A table written whose changes all
564
+ * cancelled out still appears, with count 0: the key set is every table the
565
+ * transaction wrote, which held cardinality observations rely on.
547
566
  *
548
567
  * Deliberately approximate across layers: the same key changed in two live
549
568
  * savepoint layers is counted twice. Deduplicating would cost a scan of every
@@ -553,8 +572,6 @@ export class TransactionManager {
553
572
  const result = new Map();
554
573
  const collect = (m) => {
555
574
  for (const [t, rowMap] of m) {
556
- if (rowMap.size === 0)
557
- continue;
558
575
  result.set(t, (result.get(t) ?? 0) + rowMap.size);
559
576
  }
560
577
  };
@@ -721,6 +738,7 @@ export class TransactionManager {
721
738
  this.savepointStack.length = index + 1;
722
739
  // Re-create a fresh layer for the preserved savepoint
723
740
  this.beginSavepointLayer();
741
+ this.notifyRolledBack();
724
742
  return index;
725
743
  }
726
744
  // ============================================================================