@quereus/store 4.5.0 → 4.7.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 (107) hide show
  1. package/README.md +34 -6
  2. package/dist/src/common/encoding.d.ts +4 -3
  3. package/dist/src/common/encoding.d.ts.map +1 -1
  4. package/dist/src/common/encoding.js +17 -13
  5. package/dist/src/common/encoding.js.map +1 -1
  6. package/dist/src/common/events.d.ts +31 -25
  7. package/dist/src/common/events.d.ts.map +1 -1
  8. package/dist/src/common/events.js +38 -39
  9. package/dist/src/common/events.js.map +1 -1
  10. package/dist/src/common/implicit-unique-index.d.ts +80 -0
  11. package/dist/src/common/implicit-unique-index.d.ts.map +1 -0
  12. package/dist/src/common/implicit-unique-index.js +148 -0
  13. package/dist/src/common/implicit-unique-index.js.map +1 -0
  14. package/dist/src/common/index.d.ts +7 -3
  15. package/dist/src/common/index.d.ts.map +1 -1
  16. package/dist/src/common/index.js +5 -2
  17. package/dist/src/common/index.js.map +1 -1
  18. package/dist/src/common/json-key.d.ts +30 -2
  19. package/dist/src/common/json-key.d.ts.map +1 -1
  20. package/dist/src/common/json-key.js +49 -2
  21. package/dist/src/common/json-key.js.map +1 -1
  22. package/dist/src/common/key-builder.d.ts +40 -20
  23. package/dist/src/common/key-builder.d.ts.map +1 -1
  24. package/dist/src/common/key-builder.js +31 -23
  25. package/dist/src/common/key-builder.js.map +1 -1
  26. package/dist/src/common/kv-store.d.ts +10 -0
  27. package/dist/src/common/kv-store.d.ts.map +1 -1
  28. package/dist/src/common/pk-key-resolution.d.ts +348 -0
  29. package/dist/src/common/pk-key-resolution.d.ts.map +1 -0
  30. package/dist/src/common/pk-key-resolution.js +481 -0
  31. package/dist/src/common/pk-key-resolution.js.map +1 -0
  32. package/dist/src/common/store-module-access-plan.d.ts +32 -0
  33. package/dist/src/common/store-module-access-plan.d.ts.map +1 -0
  34. package/dist/src/common/store-module-access-plan.js +428 -0
  35. package/dist/src/common/store-module-access-plan.js.map +1 -0
  36. package/dist/src/common/store-module-alter-column.d.ts +23 -0
  37. package/dist/src/common/store-module-alter-column.d.ts.map +1 -0
  38. package/dist/src/common/store-module-alter-column.js +389 -0
  39. package/dist/src/common/store-module-alter-column.js.map +1 -0
  40. package/dist/src/common/store-module-alter.d.ts +55 -0
  41. package/dist/src/common/store-module-alter.d.ts.map +1 -0
  42. package/dist/src/common/store-module-alter.js +504 -0
  43. package/dist/src/common/store-module-alter.js.map +1 -0
  44. package/dist/src/common/store-module-base.d.ts +268 -0
  45. package/dist/src/common/store-module-base.d.ts.map +1 -0
  46. package/dist/src/common/store-module-base.js +362 -0
  47. package/dist/src/common/store-module-base.js.map +1 -0
  48. package/dist/src/common/store-module-catalog.d.ts +269 -0
  49. package/dist/src/common/store-module-catalog.d.ts.map +1 -0
  50. package/dist/src/common/store-module-catalog.js +462 -0
  51. package/dist/src/common/store-module-catalog.js.map +1 -0
  52. package/dist/src/common/store-module-index-build.d.ts +88 -0
  53. package/dist/src/common/store-module-index-build.d.ts.map +1 -0
  54. package/dist/src/common/store-module-index-build.js +280 -0
  55. package/dist/src/common/store-module-index-build.js.map +1 -0
  56. package/dist/src/common/store-module-index.d.ts +116 -0
  57. package/dist/src/common/store-module-index.d.ts.map +1 -0
  58. package/dist/src/common/store-module-index.js +373 -0
  59. package/dist/src/common/store-module-index.js.map +1 -0
  60. package/dist/src/common/store-module-rename.d.ts +49 -0
  61. package/dist/src/common/store-module-rename.d.ts.map +1 -0
  62. package/dist/src/common/store-module-rename.js +268 -0
  63. package/dist/src/common/store-module-rename.js.map +1 -0
  64. package/dist/src/common/store-module-schema-rewrite.d.ts +57 -0
  65. package/dist/src/common/store-module-schema-rewrite.d.ts.map +1 -0
  66. package/dist/src/common/store-module-schema-rewrite.js +140 -0
  67. package/dist/src/common/store-module-schema-rewrite.js.map +1 -0
  68. package/dist/src/common/store-module-schema-sync.d.ts +232 -0
  69. package/dist/src/common/store-module-schema-sync.d.ts.map +1 -0
  70. package/dist/src/common/store-module-schema-sync.js +529 -0
  71. package/dist/src/common/store-module-schema-sync.js.map +1 -0
  72. package/dist/src/common/store-module.d.ts +43 -909
  73. package/dist/src/common/store-module.d.ts.map +1 -1
  74. package/dist/src/common/store-module.js +184 -3276
  75. package/dist/src/common/store-module.js.map +1 -1
  76. package/dist/src/common/store-table-base.d.ts +539 -0
  77. package/dist/src/common/store-table-base.d.ts.map +1 -0
  78. package/dist/src/common/store-table-base.js +900 -0
  79. package/dist/src/common/store-table-base.js.map +1 -0
  80. package/dist/src/common/store-table-constraints.d.ts +237 -0
  81. package/dist/src/common/store-table-constraints.d.ts.map +1 -0
  82. package/dist/src/common/store-table-constraints.js +624 -0
  83. package/dist/src/common/store-table-constraints.js.map +1 -0
  84. package/dist/src/common/store-table-scan.d.ts +452 -0
  85. package/dist/src/common/store-table-scan.d.ts.map +1 -0
  86. package/dist/src/common/store-table-scan.js +901 -0
  87. package/dist/src/common/store-table-scan.js.map +1 -0
  88. package/dist/src/common/store-table.d.ts +71 -1144
  89. package/dist/src/common/store-table.d.ts.map +1 -1
  90. package/dist/src/common/store-table.js +168 -2385
  91. package/dist/src/common/store-table.js.map +1 -1
  92. package/dist/src/testing/index.d.ts +12 -0
  93. package/dist/src/testing/index.d.ts.map +1 -0
  94. package/dist/src/testing/index.js +12 -0
  95. package/dist/src/testing/index.js.map +1 -0
  96. package/dist/src/testing/kv-assert.d.ts +19 -0
  97. package/dist/src/testing/kv-assert.d.ts.map +1 -0
  98. package/dist/src/testing/kv-assert.js +25 -0
  99. package/dist/src/testing/kv-assert.js.map +1 -0
  100. package/dist/src/testing/kv-conformance.d.ts.map +1 -1
  101. package/dist/src/testing/kv-conformance.js +1 -15
  102. package/dist/src/testing/kv-conformance.js.map +1 -1
  103. package/dist/src/testing/kv-provider-conformance.d.ts +54 -0
  104. package/dist/src/testing/kv-provider-conformance.d.ts.map +1 -0
  105. package/dist/src/testing/kv-provider-conformance.js +183 -0
  106. package/dist/src/testing/kv-provider-conformance.js.map +1 -0
  107. package/package.json +8 -8
@@ -11,57 +11,30 @@
11
11
  * - Index stores: {schema}.{table}_idx_{name} - one per secondary index
12
12
  * - {prefix}.__stats__ - Unified stats store (row counts for all tables)
13
13
  * - Catalog store: __catalog__ - DDL metadata keyed by {schema}.{table}
14
- */
15
- import type { Database, TableSchema, TableIndexSchema, VirtualTableModule, BaseModuleConfig, BestAccessPlanRequest, BestAccessPlanResult, ModuleCapabilities, SchemaChangeInfo, Schema, MappingAdvertisement, ViewSchema, MaintainedTableSchema, BackingHost, LensDeploymentSnapshot, CatalogObjectKind } from '@quereus/quereus';
16
- import type { EffectiveRowSource } from '@quereus/quereus';
17
- import type { KVStore, KVStoreProvider } from './kv-store.js';
18
- import type { StoreEventEmitter } from './events.js';
19
- import { TransactionCoordinator } from './transaction.js';
20
- import { StoreTable, type StoreTableConfig, type StoreTableModule } from './store-table.js';
21
- /**
22
- * Result of catalog rehydration.
23
14
  *
24
- * `views` / `materializedViews` are additive (existing consumers — e.g.
25
- * `quoomb-web` — read only `.errors`). Errors from any phase land in `errors`.
26
- */
27
- export interface RehydrationResult {
28
- tables: string[];
29
- indexes: string[];
30
- views: string[];
31
- materializedViews: string[];
32
- errors: RehydrationError[];
33
- }
34
- /**
35
- * An error encountered while rehydrating a single DDL entry.
36
- */
37
- export interface RehydrationError {
38
- ddl: string;
39
- error: Error;
40
- }
41
- /**
42
- * Listener the host binds via {@link StoreModule.setLensDeploymentListener} to
43
- * forward a logical `apply schema` lens deployment onward (typically to the sync
44
- * layer's basis-table lifecycle bookkeeping). Kept as a plain callback so
45
- * `@quereus/store` stays free of a `@quereus/sync` dependency; the worker — which
46
- * depends on both — wires the two together.
47
- */
48
- export type LensDeploymentListener = (db: Database, logicalSchemaName: string, snapshot: LensDeploymentSnapshot) => void | Promise<void>;
49
- /**
50
- * Configuration options for StoreModule tables.
15
+ * The class is layered across eight files, each adding one job to the one below:
16
+ * `store-module-base.ts` - module state, store handles, coordinator, name collisions
17
+ * `store-module-catalog.ts` - the catalog store: DDL entries, shutdown marker, stale-MV set
18
+ * `store-module-schema-sync.ts` - rehydration, lazy reconnect, engine schema-change subscription
19
+ * `store-module-index.ts` - CREATE / DROP INDEX and the implicit `_uc_*` store reconcile
20
+ * `store-module-alter-column.ts` - ALTER TABLE ... ALTER COLUMN
21
+ * `store-module-alter.ts` - ALTER TABLE dispatch and every other arm
22
+ * `store-module-rename.ts` - ALTER TABLE ... RENAME TO (two-phase)
23
+ * `store-module.ts` - this file: module lifecycle, capabilities, backing host
24
+ *
25
+ * Three groups read no module state and are free functions instead of layers:
26
+ * `store-module-access-plan.ts` - which access path this module advertises
27
+ * `store-module-index-build.ts` - index population + UNIQUE validation over a row stream
28
+ * `store-module-schema-rewrite.ts` - pure schema-to-schema rewrites around DDL
29
+ *
30
+ * A layer may call downward, never upward. Nothing enforces that explicitly — it holds
31
+ * because a base class cannot name a subclass member, so an upward call fails to compile.
51
32
  */
52
- export interface StoreModuleConfig extends BaseModuleConfig {
53
- /** Collation for text keys. Default: 'NOCASE'. */
54
- collation?: 'BINARY' | 'NOCASE';
55
- /**
56
- * Serialized-byte budget for a single index-build write batch (see
57
- * {@link StoreModule.buildIndexEntries}). Set from the `max_batch_bytes`
58
- * module arg; a missing / non-positive value falls back to
59
- * {@link DEFAULT_MAX_BATCH_BYTES}. Module-wide, not per-index.
60
- */
61
- maxBatchBytes?: number;
62
- /** Additional platform-specific options. */
63
- [key: string]: unknown;
64
- }
33
+ import type { BackingHost, BestAccessPlanRequest, BestAccessPlanResult, Database, LensDeploymentSnapshot, MappingAdvertisement, ModuleCapabilities, Schema, TableSchema, VirtualTableModule } from '@quereus/quereus';
34
+ import { StoreTable } from './store-table.js';
35
+ import type { StoreTableModule } from './store-table-base.js';
36
+ import { StoreModuleRename } from './store-module-rename.js';
37
+ import type { LensDeploymentListener, StoreModuleConfig } from './store-module-base.js';
65
38
  /**
66
39
  * Generic store module that works with any KVStoreProvider.
67
40
  *
@@ -75,62 +48,7 @@ export interface StoreModuleConfig extends BaseModuleConfig {
75
48
  * db.registerModule('store', module);
76
49
  * ```
77
50
  */
78
- export declare class StoreModule implements VirtualTableModule<StoreTable, StoreModuleConfig>, StoreTableModule {
79
- private provider;
80
- private stores;
81
- /**
82
- * The single transaction coordinator shared by every {@link StoreTable} this
83
- * module owns — the unit of cross-table atomicity (see {@link getCoordinator}).
84
- * Lives for the module's lifetime: a single table's drop must NOT evict it
85
- * (sibling tables still use it); only {@link closeAll} clears it.
86
- */
87
- private moduleCoordinator?;
88
- private tables;
89
- private eventEmitter?;
90
- /**
91
- * Optional listener wired by the host (the worker) to forward a logical
92
- * `apply schema` lens deployment to the sync layer's lifecycle bookkeeping.
93
- * The store module is the only basis-backing host with both persistence and a
94
- * `db` handle, so it is the forwarder; `@quereus/store` must not depend on
95
- * `@quereus/sync`, so the listener is a plain callback the worker binds.
96
- */
97
- private lensDeploymentListener?;
98
- /** Unsubscribe thunk for the engine `SchemaChangeNotifier` listener, set on first hook with a `db`. */
99
- private schemaListenerUnsub?;
100
- /** The `Database` whose notifier we subscribed to. One module instance serves one `Database`. */
101
- private subscribedDb?;
102
- /**
103
- * Serialized chain of pending catalog writes triggered by engine schema-change
104
- * events (catalog-only tag swaps). `notifyChange` invokes listeners synchronously
105
- * and does not await them, so the actual read-compare-write runs here, in order,
106
- * and is drained by `closeAll` (and `whenCatalogPersisted`) before the provider closes.
107
- */
108
- private persistQueue;
109
- /**
110
- * The JSON-serialized stale-MV set last enqueued onto {@link persistQueue} (the
111
- * `\x00meta\x00stale_mvs` value), or `undefined` when no baseline is established
112
- * for the current subscription. The schema-change listener compares each
113
- * recomputed set against this so an unrelated `table_modified` (e.g. a tag swap
114
- * that changes no MV's staleness) costs no extra fsync. Reset to `undefined`
115
- * whenever a fresh `Database` is subscribed ({@link ensureSchemaSubscription}) so
116
- * a reopened module re-establishes the baseline from the first relevant event (or
117
- * the rehydrate tail).
118
- */
119
- private lastPersistedStaleMvs;
120
- /**
121
- * Whether the provider exposes an atomic cross-store commit domain
122
- * ({@link KVStoreProvider.beginAtomicBatch}) — the capability the MV adopt fast
123
- * path's gate-5 drop hinges on. Fixed for the provider's lifetime, so cached at
124
- * construction (and used as the single gate for both writing AND reading the durable
125
- * stale-MV set). Gating the **write** on it is load-bearing for soundness, not just an
126
- * fsync saving: only an atomic-capable session — whose commits can never tear a source
127
- * from its same-module backing — ever writes the durable set, so the set a later atomic
128
- * session reads was necessarily written under that same no-tear guarantee. A persistent
129
- * non-atomic provider therefore can never leave a torn-but-"not-stale" set for an atomic
130
- * reopen to trust (it writes none); such a store falls back to the clean-shutdown marker.
131
- */
132
- private readonly atomicProvider;
133
- constructor(provider: KVStoreProvider, eventEmitter?: StoreEventEmitter);
51
+ export declare class StoreModule extends StoreModuleRename implements VirtualTableModule<StoreTable, StoreModuleConfig>, StoreTableModule {
134
52
  /**
135
53
  * Returns capability flags for this module.
136
54
  *
@@ -169,7 +87,7 @@ export declare class StoreModule implements VirtualTableModule<StoreTable, Store
169
87
  * pair — `destroy` evicts both maps, so a drop+recreate yields fresh instances
170
88
  * and the returned host is pinned to one backing-table incarnation (the engine
171
89
  * resolves hosts fresh per call, never caching them). Resolution goes through
172
- * {@link getOrReconnectTable} so a rehydrated-but-untouched (or rename-evicted)
90
+ * `StoreModuleSchemaSync.getOrReconnectTable` so a rehydrated-but-untouched (or rename-evicted)
173
91
  * backing still resolves; the ownership pre-check keeps the reconnect fallback
174
92
  * from adopting a registered table owned by a different module (`vtabModule`
175
93
  * must be this StoreModule, or a wrapper — IsolationModule — exposing it as
@@ -181,7 +99,7 @@ export declare class StoreModule implements VirtualTableModule<StoreTable, Store
181
99
  /**
182
100
  * Resolve a {@link StoreTable} this module owns, reconnecting a
183
101
  * rehydrated-but-untouched (or rename-evicted) table via
184
- * {@link getOrReconnectTable}. The ownership pre-check keeps the reconnect
102
+ * `StoreModuleSchemaSync.getOrReconnectTable`. The ownership pre-check keeps the reconnect
185
103
  * fallback from adopting a registered table owned by a DIFFERENT module:
186
104
  * `vtabModule` must be this StoreModule, or a wrapper (IsolationModule)
187
105
  * exposing it as `underlying`. Returns undefined for an unknown/non-owned
@@ -203,16 +121,27 @@ export declare class StoreModule implements VirtualTableModule<StoreTable, Store
203
121
  * pending state on its own (none when the table is freshly reconnected).
204
122
  */
205
123
  getTableForExternalWrite(db: Database, schemaName: string, tableName: string): StoreTable | undefined;
206
- /**
207
- * Get the event emitter for this module.
208
- */
209
- getEventEmitter(): StoreEventEmitter | undefined;
210
124
  /**
211
125
  * Bind (or clear, with `undefined`) the lens-deployment forwarder. The host
212
126
  * (worker) calls this to route a logical `apply schema`'s deployment to the
213
127
  * sync layer; see {@link notifyLensDeployment}.
214
128
  */
215
129
  setLensDeploymentListener(listener: LensDeploymentListener | undefined): void;
130
+ /**
131
+ * Engine `onRegister` hook: subscribe to the database's schema-change notifier the
132
+ * moment this module is registered, before any statement can run.
133
+ *
134
+ * Views and materialized views never route through a module table hook, so without
135
+ * this the module would only learn its `Database` from the first
136
+ * `create`/`connect`/`alterTable`/`rehydrateCatalog` — and a `create view` issued
137
+ * before that (the first statement of a brand-new database, say) would fire
138
+ * `view_added` at a module that is not listening yet and silently never persist.
139
+ *
140
+ * Idempotent with the lazy call sites; a second `Database` logs
141
+ * `ensureSchemaSubscription`'s "different Database" warning here rather than at first
142
+ * table use (a warning, not a throw — one module instance still serves one Database).
143
+ */
144
+ onRegister(db: Database, _moduleName: string): void;
216
145
  /**
217
146
  * Engine `notifyLensDeployment` hook: a logical `apply schema X` fires this on
218
147
  * every registered module once the lens catalog mutation + snapshot rotation
@@ -227,60 +156,6 @@ export declare class StoreModule implements VirtualTableModule<StoreTable, Store
227
156
  * warning, not a failed deploy.
228
157
  */
229
158
  notifyLensDeployment(db: Database, logicalSchemaName: string, snapshot: LensDeploymentSnapshot): Promise<void>;
230
- /**
231
- * Get the KVStoreProvider used by this module.
232
- */
233
- getProvider(): KVStoreProvider;
234
- /**
235
- * Build the set of physical store names this module's data/index stores
236
- * currently occupy in `schemaName`, mapping each name to a human description
237
- * of the logical object that owns it (for sited collision messages).
238
- *
239
- * Physical store names are built by string concatenation (`{schema}.{table}` /
240
- * `{schema}.{table}_idx_{index}`) and the `_idx_` delimiter is itself a legal
241
- * substring of any identifier, so two distinct logical objects (e.g. index
242
- * `archive` on `t` and a sibling table `t_idx_archive`) can collapse to the
243
- * same physical name. This set is the authoritative occupancy used by
244
- * {@link assertStoreNameFree} to reject such a collision at CREATE time.
245
- *
246
- * The occupancy is the union of two sources, robust to lazy connection and the
247
- * isolation wrapper (see the ticket's "Enumeration source" section):
248
- * 1. `this.tables` — every store table this module touched this session.
249
- * 2. the target schema's catalog tables whose `vtabModule === this` and which
250
- * are not views — store-backed tables not yet lazily connected.
251
- * Names embed the schema prefix, so cross-schema entries never collide and no
252
- * per-schema filter on `this.tables` is needed. Memory-backed siblings and
253
- * views own no store in this provider and are excluded (the `=== this` /
254
- * `!isView` filter) to avoid false-positive rejects.
255
- *
256
- * No self-exclusion: at each guarded call the candidate object is not yet
257
- * registered (create/createIndex run before the engine adds it) so it cannot
258
- * self-collide; and for renameTable the renamed table's OWN stores stay in the
259
- * set deliberately. Any overlap between a name the rename introduces and an
260
- * own current store is a footprint-swap rename (`t` with index `x` → `t_idx_x`,
261
- * or table `u_idx_x` with index `x` → `u`) that providers cannot relocate
262
- * safely — relocation order determines whether a source is clobbered before it
263
- * is moved — while no benign rename produces such an overlap. Keeping own
264
- * stores in the set therefore causes no false rejects and keeps the
265
- * reject-before-any-side-effect guarantee uniform.
266
- */
267
- private collectOccupiedStoreNames;
268
- /**
269
- * Throws `StatusCode.ERROR` when `candidate` (a physical store name produced by
270
- * the key-builder for an object about to be created/renamed) already names an
271
- * existing data or index store in `schemaName`. `candidateDesc` describes the
272
- * incoming logical object; the message names the candidate physical store and
273
- * both conflicting logical objects and is actionable (rename one of them).
274
- *
275
- * Must run BEFORE any storage side-effect (`getStore` / `getIndexStore` / the
276
- * physical relocation): the provider opens/creates the store eagerly, so a
277
- * guard that ran after would already have aliased the colliding object's store.
278
- *
279
- * Callers checking several candidates against the same occupancy (renameTable
280
- * checks the new data store plus every relocated index store) pass a
281
- * precomputed `occupied` map so the occupancy is collected once, not per call.
282
- */
283
- private assertStoreNameFree;
284
159
  /**
285
160
  * Creates a new store-backed table.
286
161
  * Called by CREATE TABLE.
@@ -334,330 +209,6 @@ export declare class StoreModule implements VirtualTableModule<StoreTable, Store
334
209
  * provider no-ops on absent stores and `removeTableDDL` no-ops on an absent key.
335
210
  */
336
211
  private tearDownTableStorage;
337
- /**
338
- * Returns the connected StoreTable for `schemaName.tableName`, lazily
339
- * reconnecting from the engine's schema registry when absent.
340
- *
341
- * `renameTable` evicts the old key from `this.tables` and expects the next
342
- * `connect()` to repopulate under the new name, but `apply schema` can run
343
- * follow-up DDL (ALTER TABLE, CREATE/DROP INDEX) against the new name
344
- * without an intervening connect. Mirror connect()'s schemaManager lookup
345
- * so that DDL finds the moved table. Safe for the index paths because
346
- * SchemaManager calls the module BEFORE mutating the registered table
347
- * schema, so the reconnected cache matches what a connected instance holds.
348
- */
349
- private getOrReconnectTable;
350
- /**
351
- * Creates an index on a store-backed table.
352
- *
353
- * `rows` — an {@link EffectiveRowSource} supplied by a wrapper module (the isolation
354
- * layer) — replaces this module's own rows as the set the UNIQUE duplicate check judges.
355
- * When present the check runs UP FRONT, before `getIndexStore` opens the index-store
356
- * directory, so a rejection leaves no directory behind; and `buildIndexEntries` then
357
- * populates the physical index from this module's own (committed) rows with its in-pass
358
- * dup check disabled. Those two row sets legitimately differ — the wrapper's transaction
359
- * may have deleted a committed duplicate — and an index entry with no live row behind it
360
- * is harmless (see the entry-resolution note in `buildIndexEntries`).
361
- */
362
- createIndex(db: Database, schemaName: string, tableName: string, indexSchema: TableIndexSchema, rows?: EffectiveRowSource): Promise<void>;
363
- /**
364
- * Drops an index on a store-backed table.
365
- *
366
- * Mirrors createIndex: refreshes the connected StoreTable's cached
367
- * tableSchema (removing the index entry and any UNIQUE constraint
368
- * synthesized from it, tagged with `derivedFromIndex`), releases the
369
- * cached index-store handle, and tears down the underlying index store.
370
- */
371
- dropIndex(db: Database, schemaName: string, tableName: string, indexName: string): Promise<void>;
372
- /**
373
- * Build index entries for all existing rows in a table.
374
- *
375
- * `dataEntries` is the row stream to index. Callers choose its visibility:
376
- * `createIndex` passes the table's EFFECTIVE stream (committed + pending, so
377
- * an open transaction's rows are indexed too), while `rebuildSecondaryIndexes`
378
- * passes the raw committed stream — it runs immediately after an ALTER has
379
- * re-encoded the data store in place, and any pending ops still address the
380
- * pre-ALTER key bytes.
381
- *
382
- * For UNIQUE indexes, performs an in-pass duplicate check (honoring partial
383
- * predicates and SQL NULL semantics: multiple NULLs are allowed) and throws
384
- * CONSTRAINT before any entries are written. Mirrors the memory module's
385
- * populateNewIndex so `CREATE UNIQUE INDEX` over duplicated data fails
386
- * atomically. `skipDuplicateCheck` suppresses it: `createIndex` sets it when a
387
- * wrapper module supplied the rows to judge, having already validated them (see
388
- * {@link validateUniqueIndexOverRows}). Judging `dataEntries` too would reject a
389
- * committed duplicate the wrapper's transaction has already deleted.
390
- *
391
- * `normalizers` MUST be the owning connection's `db.getKeyNormalizerResolver()` —
392
- * the same resolver `StoreTable.encodeOptions` carries. A rebuild that resolved
393
- * collations any other way would re-encode the PK suffix under different bytes than
394
- * the table writes at maintenance time, silently corrupting the index.
395
- */
396
- private buildIndexEntries;
397
- /**
398
- * Clear and rebuild every secondary index of `schema` against the (already
399
- * re-encoded) data store. Secondary-index keys embed the PK suffix, so they must
400
- * be rewritten whenever the data-store PK key bytes change — which happens both
401
- * on `ALTER PRIMARY KEY` (the PK columns change) and on an `ALTER COLUMN … SET
402
- * COLLATE` on a PK member (a PK column's key collation changes). Shared by both
403
- * arms so the clear-then-rebuild stays identical. `schema` must already be the
404
- * post-ALTER schema (its `primaryKeyDefinition` + column collations drive the new
405
- * PK-suffix encoding via {@link buildIndexEntries}). `normalizers` is the owning
406
- * connection's `db.getKeyNormalizerResolver()` — see {@link buildIndexEntries}.
407
- *
408
- * NOTE: reads the data store committed-only and writes the index stores outside the
409
- * coordinator. Sound only because every caller calls {@link ddlCommitPendingOps}
410
- * first, so this module's "committed" is "everything live". A caller that skips that
411
- * flush would rebuild an index missing its transaction's pending rows.
412
- *
413
- * `skipDuplicateCheck` suppresses {@link buildIndexEntries}' in-pass UNIQUE check.
414
- * The value-rewriting / key-transform ALTER COLUMN arm sets it: its pre-mutation
415
- * UNIQUE re-validation already judged the ISSUING connection's effective rows (a
416
- * wrapper's overlay included), and this rebuild sees only THIS module's committed
417
- * rows — a superset that may retain a row the wrapper's transaction has deleted, so
418
- * judging it here would spuriously reject a duplicate pair the probe correctly
419
- * accepted. Mirrors the memory module's deliberately non-enforcing base rebuild
420
- * (the probe is the only guard).
421
- */
422
- private rebuildSecondaryIndexes;
423
- /**
424
- * Validates `rows` against a UNIQUE constraint, throwing `CONSTRAINT` on the first
425
- * duplicate before any schema mutation. Used by `ADD CONSTRAINT UNIQUE` (validate
426
- * against the current collation) and by `SET COLLATE` (pass an `updatedSchema` whose
427
- * altered column carries the NEW collation, so the dedup is performed under it).
428
- * Mirrors the duplicate detection in {@link buildIndexEntries}: a `seen` Set keyed on
429
- * a per-column collation-aware signature of the constrained values, with SQL NULL
430
- * semantics (a row with any NULL constrained value never counts as a duplicate) and
431
- * the partial `predicate` honored.
432
- *
433
- * `rows` MUST be the rows the DDL-issuing connection can SEE. Ordinarily that is this
434
- * table's EFFECTIVE stream (committed rows merged with the open transaction's own
435
- * pending puts/deletes — `StoreTable.iterateEffectiveEntries`, adapted by
436
- * {@link rowsFromEntries}); when a wrapper module holds the pending rows outside this
437
- * module it hands them down as an `EffectiveRowSource` instead. Either way, a
438
- * committed-only stream would let a duplicate inserted earlier in the same transaction
439
- * slip past validation and land in the table once the transaction commits.
440
- *
441
- * No index store is written — store UNIQUE enforcement is a full-scan over
442
- * `uniqueConstraints` at write time. The signature is built by
443
- * {@link dedupeRowSignature} with one normalizer per constrained column, resolved from
444
- * `tableSchema.columns[idx].collation` through the connection's
445
- * `db.getKeyNormalizerResolver()`, so a per-column collation registered with
446
- * `db.registerCollation` is honored (matching write-time `compareSqlValues`
447
- * enforcement). A comparator-only collation raises: rows that cannot be bucketed
448
- * cannot be deduped. Columns whose declared type can never hold text take the
449
- * identity normalizer and so never raise.
450
- */
451
- private validateUniqueOverExistingRows;
452
- /**
453
- * Index-shaped twin of {@link validateUniqueOverExistingRows}, used by
454
- * {@link createIndex} when a wrapper module supplies the rows to judge. Dedupes on the
455
- * index's own columns under the index column's COLLATE (falling back to the table
456
- * column's), so it enforces exactly what `buildIndexEntries`' suppressed in-pass check
457
- * would have — over the wrapper's rows instead of this module's committed ones.
458
- */
459
- private validateUniqueIndexOverRows;
460
- /**
461
- * Flush the module's buffered writes before a DDL operation that physically
462
- * rewrites or relocates storage.
463
- *
464
- * Such a DDL cannot honor the coordinator's buffer: pending ops are
465
- * `(keyBytes, valueBytes, store)` triples computed at DML time under the
466
- * PRE-DDL schema, and every physical rewrite (`StoreTable.rekeyRows` /
467
- * `migrateRows`, the provider's directory move) reads and writes the COMMITTED
468
- * store directly. Replaying stale-schema ops over the rewritten store on the
469
- * eventual commit corrupts, loses, or misfiles those rows. So an `ALTER TABLE`
470
- * that touches storage is effectively DDL-committing on a store-backed table:
471
- * flush NOW, before the first physical read, so the rewrite sees every live row
472
- * and there is nothing left to replay afterwards.
473
- *
474
- * Consequences, both deliberate:
475
- * - The coordinator is module-wide, so this commits the WHOLE module
476
- * transaction — every table's pending ops, not just the altered/renamed
477
- * table's — in one all-or-nothing batch. An ALTER cannot half-commit some
478
- * sibling tables.
479
- * - Validation running AFTER this point (e.g. `rekeyRows`' duplicate-key pass,
480
- * which must see pending rows because a pending insert can itself be the
481
- * duplicate) throws with the enclosing transaction already flushed. The store
482
- * stays unmutated — only the transaction is gone. Validation that does NOT
483
- * need pending rows belongs before the call, so the transaction survives it.
484
- *
485
- * Subsequent `commit()` calls on the same coordinator are no-ops
486
- * (`inTransaction` is cleared), which keeps the enclosing transaction safe.
487
- * The savepoint stack is cleared too, so a later `ROLLBACK TO` / `RELEASE` that
488
- * the engine still broadcasts warns and degrades rather than throwing — see
489
- * `TransactionCoordinator.rollbackToSavepoint`, which mirrors the memory
490
- * module's identical posture.
491
- */
492
- private ddlCommitPendingOps;
493
- /**
494
- * Alters an existing store table's structure. Resolves the table, captures the
495
- * pre-alter schema, and dispatches to the per-change-type `alter*` helper below;
496
- * the helper does the arm's work (row migration, physical re-key, constraint
497
- * validation, DDL persist) and returns the updated TableSchema for the engine to
498
- * register. The shared preamble (schema subscription, reconnect, not-found throw,
499
- * `defaultNotNull`) lives here so every arm sees the same resolved state.
500
- */
501
- alterTable(db: Database, schemaName: string, tableName: string, change: SchemaChangeInfo, rows?: EffectiveRowSource): Promise<TableSchema>;
502
- /**
503
- * Build / tear down the physical index stores backing implicit UNIQUE indexes
504
- * (`_uc_*`) after an ALTER, by diffing the implicit-index NAMES of the old vs new
505
- * constraint sets:
506
- * - a name newly PRESENT (ADD CONSTRAINT UNIQUE, or the target half of a
507
- * RENAME CONSTRAINT / rename of an unnamed UC's column) has its physical store
508
- * populated from this module's effective rows;
509
- * - a name newly ABSENT (DROP CONSTRAINT UNIQUE, or the source half of a rename)
510
- * has its store torn down, so a later re-ADD does not reopen stale entries.
511
- *
512
- * The SCHEMA entry itself is materialized/removed by `withImplicitUniqueIndexes`
513
- * on the arm's `table.updateSchema`; only the physical store needs this. Runs after
514
- * every ALTER but is a no-op whenever the implicit-index name set is unchanged
515
- * (incl. PK / collation / data-type ALTERs, whose same-name physical re-encode is
516
- * already done by {@link rebuildSecondaryIndexes}).
517
- *
518
- * `oldSchema` carries the pre-ALTER `uniqueConstraints` (its `.indexes` may be
519
- * de-materialized — the diff reads `uniqueConstraints`, not `.indexes`).
520
- *
521
- * NOTE: the build populates from THIS module's own effective rows (committed + this
522
- * transaction's pending), never a wrapper's — mirroring `createIndex`. Under the
523
- * isolation layer the wrapper's rows are what ADD CONSTRAINT's validation judged;
524
- * the physical index legitimately fills from this module's committed rows and an
525
- * entry with no live row behind it is harmless (both readers resolve to the live
526
- * row). Duplicates were already rejected by `validateUniqueOverExistingRows` (ADD)
527
- * or cannot exist (RENAME leaves data unchanged), so the in-pass check is skipped.
528
- */
529
- private reconcileImplicitUniqueIndexStores;
530
- /**
531
- * The physical index-store names a table owns for DROP / RENAME TABLE to relocate
532
- * or reclaim: the MATERIALIZED index set — every explicit `CREATE INDEX` PLUS the
533
- * hidden `_uc_*` realizing each plain UNIQUE. Reading only `getSchema().indexes`
534
- * (the engine-facing schema, which carries no `_uc_*`) would strand a `_uc_*` store
535
- * on disk after DROP TABLE, and — worse — after RENAME TABLE leave the renamed
536
- * table seeking a freshly-created EMPTY `_uc_*` store, silently accepting a
537
- * duplicate of a pre-rename row. Prefers the cached StoreTable's own materialized
538
- * schema; falls back to materializing the schema-manager copy when the instance is
539
- * already gone.
540
- */
541
- private materializedIndexNames;
542
- /** Close + delete the physical store of an implicit UNIQUE index. Mirrors `dropIndex`. */
543
- private tearDownImplicitUniqueIndexStore;
544
- /**
545
- * ADD COLUMN arm of {@link alterTable}: append the new column, eagerly migrate
546
- * each row (literal or per-row backfill), and persist. Behavior-preserving
547
- * extraction of the former `switch` arm.
548
- *
549
- * The store always appends. A caller-chosen `insertAtIndex` (module-API only; SQL never
550
- * produces one) is rejected unless it names the append position, rather than silently
551
- * landing the column somewhere the caller did not ask for.
552
- */
553
- private alterAddColumn;
554
- /** DROP COLUMN arm of {@link alterTable}: drop the column slot, reindex PK / indexes /
555
- * UNIQUE, migrate rows, and persist. Behavior-preserving extraction. */
556
- private alterDropColumn;
557
- /** RENAME COLUMN arm of {@link alterTable}: schema-only rewrite (columns, indexes, self-FK,
558
- * in-place predicate / CHECK AST rewrite) and persist. Behavior-preserving extraction. */
559
- private alterRenameColumn;
560
- /** ALTER PRIMARY KEY arm of {@link alterTable}: physically re-key the data store, rebuild
561
- * secondary indexes, and persist. Behavior-preserving extraction. */
562
- private alterPrimaryKeyChange;
563
- /** ADD CONSTRAINT arm of {@link alterTable}: validate existing rows as the constraint kind
564
- * (UNIQUE / FOREIGN KEY / CHECK) requires, then persist. Behavior-preserving extraction. */
565
- private alterAddConstraint;
566
- /** DROP CONSTRAINT arm of {@link alterTable}: schema-only catalog rewrite dropping a named
567
- * constraint, then persist. Behavior-preserving extraction. */
568
- private alterDropConstraint;
569
- /** RENAME CONSTRAINT arm of {@link alterTable}: schema-only rename of a named constraint,
570
- * then persist. A renamed named-UNIQUE changes its implicit `_uc_*` index name, so
571
- * `reconcileImplicitUniqueIndexStores` (run after this arm) MOVES the physical store —
572
- * tears down the old-named store and rebuilds the new-named one from effective rows. */
573
- private alterRenameConstraint;
574
- /** ALTER COLUMN arm of {@link alterTable}: change one attribute (NOT NULL / data type /
575
- * default / collation), running the physical re-key + existing-row re-validation the
576
- * collation / PK paths require, then persist. Behavior-preserving extraction. */
577
- private alterColumnChange;
578
- /**
579
- * SET NOT NULL / DROP NOT NULL sub-branch of {@link alterColumnChange}. Returns the
580
- * new column schema, or null when the column is already in the desired nullability
581
- * (the pre-refactor `return oldSchema` no-op). Mutates nothing: the NULL-backfill
582
- * probe is throw-only, and the backfill itself is returned as a deferred
583
- * `valueConvert` (null → DEFAULT) the caller applies only after every throw-only
584
- * check — including the UNIQUE re-validation over the backfilled values — has passed.
585
- *
586
- * `rows` is the wrapper-supplied effective row source (the isolation overlay). When present,
587
- * the reject-vs-backfill decision scans it instead of `table.rowsWithNullAtIndex`: behind the
588
- * isolation layer the issuer's pending inserts live in the wrapper's overlay, not this store,
589
- * so the store's own count would miss them and wrongly accept the ALTER. The committed-store
590
- * `mapRowsAtIndex` backfill is unchanged — the overlay-resident pending rows are the isolation
591
- * layer's job (its overlay migration), this store only owns its committed rows.
592
- */
593
- private alterColumnSetNotNull;
594
- /**
595
- * SET DATA TYPE sub-branch of {@link alterColumnChange}. Returns the retyped column
596
- * schema. Mutates nothing: for every retype between DIFFERENT logical types, a
597
- * throw-only convert pass over the live rows proves every value convertible, and the
598
- * conversion itself is returned as a deferred `valueConvert` the caller applies only
599
- * after every throw-only check — including the UNIQUE re-validation over the converted
600
- * values — has passed. Gated on logical-type IDENTITY, not the physical storage class:
601
- * `inferType` flattens aliases to the shared type object (`varchar(50)` IS `TEXT_TYPE`),
602
- * so an alias retype is schema-only, while a same-class retype (text → date) still
603
- * rejects values the new type refuses and rewrites the rest to the new type's
604
- * canonical spelling ('2024-06-05T00:00:00Z' → '2024-06-05') — exactly as an INSERT
605
- * would have stored them.
606
- *
607
- * A retype of a PRIMARY KEY member is refused here rather than converted: the rewrite
608
- * below is `mapRowsAtIndex`, a payload-only rewrite that reuses `entry.key` verbatim, so
609
- * a PK column's physical key bytes would stay encoded under the OLD type while the value
610
- * moves to the new one — unfindable by any lookup under the new encoding, and the same
611
- * `keyTransformChanged` path in the caller would re-key from the pre-rewrite values before
612
- * this rewrite ever ran (see the NOTE above the `valueConvert` block in
613
- * {@link alterColumnChange}). Every live SQL caller already refuses this earlier (the
614
- * engine's `runAlterColumn` and the materialized-view reshape's inexpressibility check),
615
- * so this only guards a direct module call — mirrors the memory backend's carve-out
616
- * (`MemoryTableManager.alterColumn`).
617
- */
618
- private alterColumnSetDataType;
619
- /**
620
- * SET COLLATE sub-branch of {@link alterColumnChange}. Returns the recollated column
621
- * schema with `collationChanged` set when the collation bytes actually change (a bare
622
- * metadata flip keeps it false), or null when the column is already explicit in the
623
- * desired collation (the pre-refactor `return oldSchema` no-op). Synchronous — no
624
- * row scan happens here; the caller runs the re-validation / re-key the flag gates.
625
- */
626
- private alterColumnSetCollation;
627
- /**
628
- * Rename a store-backed table.
629
- *
630
- * Drops every in-memory reference to the old name (so the coordinator, open
631
- * handles, and cached StoreTable instance don't linger with stale paths),
632
- * delegates physical storage relocation to the provider, then rewrites the
633
- * persistent catalog DDL under the new key. After this returns, the next
634
- * access to `newName` will reconnect via `connect()` and open fresh stores
635
- * against the moved directories.
636
- */
637
- renameTable(db: Database, schemaName: string, oldName: string, newName: string): Promise<void>;
638
- /**
639
- * Second phase of a two-phase RENAME TABLE (see {@link renameTable}). The engine calls
640
- * this at the END of ALTER TABLE ... RENAME TO — AFTER its `propagateTableRename` has
641
- * rewritten every dependent object that named `oldName` and enqueued their corrective
642
- * catalog writes onto {@link persistQueue}. `renameTable` deliberately left `oldName`'s
643
- * catalog entry in place; here we drain those dependent writes to durability and only
644
- * THEN drop the old entry. During the window both entries coexist on disk, so every
645
- * dependent resolves against one of them and every intermediate catalog set rehydrates
646
- * into a working database.
647
- *
648
- * The old-entry delete rides `persistQueue` behind the dependents' already-enqueued
649
- * writes (FIFO), so it can only run after them, and the drain below awaits the whole
650
- * chain. Errors are swallowed+logged (the {@link enqueuePersist} contract): a failed
651
- * delete leaves the old entry present — a visible, droppable orphan, strictly safer
652
- * than a dependent stranded against a vanished table.
653
- *
654
- * NOTE: full cross-table atomicity — bundling this old-entry delete together with every
655
- * dependent rewrite into one `provider.beginAtomicBatch` commit — would eliminate even
656
- * the transient two-entry window (and the physical-move orphan `renameTableStores`
657
- * leaves), but only on atomic providers, and the dependent set is known only to the
658
- * engine post-propagate. That is the atomic-provider hardening path; out of scope here.
659
- */
660
- finalizeRename(_db: Database, schemaName: string, oldName: string, _newName: string): Promise<void>;
661
212
  /**
662
213
  * Modern access planning interface.
663
214
  *
@@ -679,431 +230,14 @@ export declare class StoreModule implements VirtualTableModule<StoreTable, Store
679
230
  * ORDER-PRESERVING — byte order and comparator order must coincide, or the seek would
680
231
  * under-fetch and the ordering advertisement would elide a Sort it must not. That gate
681
232
  * needs the connection's collation registry, hence `db` is threaded down.
682
- */
683
- getBestAccessPlan(db: Database, tableInfo: TableSchema, request: BestAccessPlanRequest): BestAccessPlanResult;
684
- private computeBestAccessPlan;
685
- /**
686
- * Build the access plan for one secondary index against `request`, or null when
687
- * the index is not usable for this predicate.
688
- *
689
- * Usable = a contiguous leading-prefix EQ on the index columns (an index seek /
690
- * point), or a LT/LE/GT/GE range on the LEADING index column. These mirror the
691
- * two windows {@link StoreTable.analyzeIndexAccess} can build.
692
- *
693
- * **Collation-safety guard against under-fetch.** The store's index-column
694
- * window is encoded under the table key collation K, but `matchesFilters`
695
- * compares under the COLUMN's declared collation. Marking a filter handled drops
696
- * the residual Filter, so the K-window must be a guaranteed SUPERSET of the
697
- * qualifying rows. That holds only when K is coarser-or-equal to the column's
698
- * declared collation. To stay provably safe with minimal logic we mark the
699
- * covered filters handled — setting `indexName` + `seekColumns` — only when every
700
- * seek column is non-text, OR its declared collation equals K, OR (K = NOCASE
701
- * while the column is BINARY, i.e. K strictly coarser). Otherwise we return a
702
- * cost-only plan (cheaper cost, filters unhandled, residual retained — correct,
703
- * just not sped up). K itself always keys: `StoreTable`'s constructor rejects a
704
- * table whose key encoding would need a collation it cannot resolve to a normalizer.
705
- *
706
- * NOTE: the coarser-K relaxation is sound for EQUALITY only. A RANGE window equates
707
- * memcmp of K-normalized bytes with C's comparator order, which a merely coarser K does
708
- * not give — under K = NOCASE and C = BINARY, 'K' (U+212A) is `> 'z'` yet keys as 'k',
709
- * before 'z'. So the range arm demands `C === K` *and* K's `orderPreserving` assertion,
710
- * via the shared {@link keyOrderMatchesCollation}; `StoreTable.analyzeIndexAccess`
711
- * declines the same windows, so the two decisions cannot disagree. The cost is that a
712
- * default-K (NOCASE) table with an index on a plain BINARY text column loses its index
713
- * RANGE seek and falls back to the cost-only plan; EQ seeks are unchanged.
714
- */
715
- private tryIndexAccessPlan;
716
- /**
717
- * Compute the PK-ordering advertisement for a scan-style plan. Returns the
718
- * `providesOrdering` / `monotonicOn` / `supportsAsofRight` fields for a plan
719
- * whose iteration is driven by the primary-key key order (full scan or PK
720
- * range scan).
721
- *
722
- * `providesOrdering` is set only when it actually matches what the caller
723
- * needs:
724
- * - When the request carries `requiredOrdering`, claim it only if the
725
- * requested keys form a prefix of the PK with matching directions.
726
- * Claiming PK order against an `ORDER BY <other column>` would cause
727
- * the absorb-Sort rule to drop the Sort and yield wrong-order rows.
728
- * - When no `requiredOrdering` is present, advertise the full PK
729
- * ordering so downstream rules (merge-join, sort elision after a
730
- * filter) can opportunistically use it.
731
- *
732
- * `monotonicOn` reflects the access path itself and is independent of any
733
- * `requiredOrdering`; it always advertises the leading PK column. Strict
734
- * monotonicity is claimed iff the PK is single-column — composite PKs can
735
- * repeat values on the leading column.
736
- *
737
- * Returns an empty object when there is no PK (heap-only table) — without a
738
- * leading key column there is no natural emit order.
739
- *
740
- * Every claim here is about the PHYSICAL key-byte order the store iterates in, but the
741
- * consumers of `providesOrdering` / `monotonicOn` reason in the columns' COLLATION order.
742
- * `orderPreservingPrefix` (from {@link pkOrderPreservingPrefixLength}) is how many leading
743
- * PK members those two orders provably agree on: the advertisement is truncated to that
744
- * prefix, and voided entirely when even the leading member disagrees — otherwise the
745
- * absorb-Sort rule would elide a Sort and hand the caller byte-ordered rows.
746
- */
747
- private buildPkOrderingAdvertisement;
748
- /**
749
- * Get or create a data store for a table.
750
- */
751
- getStore(schemaName: string, tableName: string, _config: StoreTableConfig): Promise<KVStore>;
752
- /**
753
- * Get or create an index store for a table.
754
- */
755
- getIndexStore(schemaName: string, tableName: string, indexName: string): Promise<KVStore>;
756
- /**
757
- * Get or create a stats store for a table.
758
- */
759
- getStatsStore(schemaName: string, tableName: string): Promise<KVStore>;
760
- /**
761
- * Get (lazily constructing) the SINGLE transaction coordinator shared by every
762
- * table this module owns — the unit of cross-table atomicity. Every
763
- * {@link StoreTable} attaches to the same instance, so a transaction touching
764
- * several of the module's tables commits/rolls back as one all-or-nothing
765
- * batch.
766
- *
767
- * Synchronously constructible: ops are addressed by explicit store handle, so
768
- * the coordinator never needs to open a store to be created. This lets callers
769
- * that must stay synchronous (e.g. a `BackingHost.connect()`) obtain a working
770
- * coordinator before any table's store has been opened.
771
- */
772
- getCoordinator(): TransactionCoordinator;
773
- /**
774
- * Build the catalog entry for a table: its CREATE TABLE DDL, one
775
- * `CREATE [UNIQUE] INDEX` statement per persistable secondary index, and one
776
- * `alter index … set tags (…)` statement per exposed implicit index carrying
777
- * user tags, newline-joined into a single multi-statement bundle keyed by
778
- * `{schema}.{table}`.
779
- *
780
- * Bundling the indexes into the table's own entry means every existing
781
- * re-persist path — `saveTableDDL` (each `alterTable` arm, `renameTable`) and
782
- * the `table_modified` listener (`persistCatalogIfChanged`) — carries the
783
- * indexes along for free, and `removeTableDDL` drops them with the table. The
784
- * bundle is consumed on reopen by `rehydrateCatalog` → `importCatalog`, whose
785
- * `parser.parseAll` splits it AST-by-AST (never on `\n`) and imports each
786
- * statement in document order, so the table registers before its indexes.
787
- *
788
- * Both the table DDL and the index DDL are emitted without a `db` arg, keeping
789
- * the persistence-safe fully-qualified form. Hidden implicit covering indexes
790
- * (the auto-built BTree backing a declared UNIQUE constraint) are excluded —
791
- * they are a backing detail that round-trips via the table's UNIQUE constraint,
792
- * not as a standalone CREATE INDEX. For store tables `buildTableSchemaFromAST`
793
- * synthesizes none of those, so in practice every index is included; the guard
794
- * is defensive. A `CREATE UNIQUE INDEX`'s derived UNIQUE constraint is already
795
- * excluded from the table DDL by the generator, so it round-trips solely via
796
- * its own `CREATE UNIQUE INDEX` line — no doubling.
797
- *
798
- * An *exposed* implicit index (a non-derived UNIQUE constraint tagged
799
- * `quereus.expose_implicit_index = true`, never materialized in store mode)
800
- * must NOT get a synthetic `CREATE INDEX` line — re-import would materialize a
801
- * real `IndexSchema` and change the store-mode shape. Its user tags
802
- * (`UniqueConstraintSchema.exposedIndexTags`) instead ride a trailing
803
- * `alter index … set tags` line, which `importDDL` re-applies silently after
804
- * the CREATE TABLE in the same entry has registered the constraint. Empty tag
805
- * records emit no line, and `exposedImplicitIndexes` returns `[]` for
806
- * memory-mode tables (name materialized) and orders descriptors by the
807
- * `uniqueConstraints` array, so the bundle stays byte-deterministic — which
808
- * the compare-write in `persistCatalogIfChanged` relies on.
809
- */
810
- private buildCatalogEntry;
811
- /**
812
- * Encode a bundle of persisted schema text (DDL) for the catalog store.
813
- * The guard is {@link assertPersistableDdlText} — see it for why the FULL text
814
- * is checked, not just the object's name.
815
- */
816
- private encodeCatalogDDL;
817
- /**
818
- * Save table DDL (bundled with its secondary index DDL) to the catalog store.
819
- */
820
- saveTableDDL(tableSchema: TableSchema): Promise<void>;
821
- /**
822
- * Load all DDL **values** from the catalog store (keys discarded).
823
- * Used to restore persisted tables on startup and by tests asserting persisted DDL.
824
- * Note: this returns table, view, AND materialized-view entries intermixed —
825
- * {@link rehydrateCatalog} uses {@link loadCatalogEntries} (keys retained) to
826
- * classify them. Meta entries (e.g. the clean-shutdown marker) are not DDL
827
- * and are filtered out.
828
- */
829
- loadAllDDL(): Promise<string[]>;
830
- /**
831
- * Load every catalog entry as `{ key, ddl }`. Unlike {@link loadAllDDL} (values
832
- * only) this retains the key so {@link rehydrateCatalog} can classify each entry
833
- * (table / view / materialized view) by its reserved key prefix.
834
- */
835
- private loadCatalogEntries;
836
- /**
837
- * Rehydrate the persisted catalog into the in-memory schema manager, in
838
- * dependency order.
839
- *
840
- * Establishes the engine schema-change subscription up front (so a reopened DB
841
- * persists subsequent DDL even when its first post-reopen statement is a view/MV,
842
- * which never routes through a module hook — all the lazy subscription points are
843
- * table hooks). Then loads every catalog entry once, classifies each by its key
844
- * prefix into {tables, views, materialized views}, and imports in three phases:
845
- *
846
- * 1. **Tables** — `importCatalog` (connect to existing storage; refresh connected
847
- * `StoreTable` schemas).
848
- * 2. **Views** — `importCatalog` (engine silent-register; body validation deferred
849
- * to query time, so order among views — and view-over-MV / view-over-view —
850
- * does not matter, and no schema-change event fires → phase 2 writes nothing).
851
- * 3. **Materialized views** — `importCatalog` per entry (engine re-materialize:
852
- * rebuilds the memory backing from current source data, re-registers row-time
853
- * maintenance, re-runs the eligibility gate — the same core the create emitter
854
- * uses, but silent: no `materialized_view_added` fires, so phase 3 writes
855
- * nothing back to the catalog). A store-hosted backing that phase 1 already
856
- * rehydrated is **adopted without the refill** when the engine's adopt gates
857
- * pass — see the clean-shutdown marker below.
858
- *
859
- * **Clean-shutdown marker.** Before anything loads, the reserved
860
- * `\x00meta\x00clean_shutdown` catalog entry (written by {@link closeAll} after
861
- * every batch flushed) is consumed: parsed into `{ trusted, staleAtClose }`, then
862
- * **deleted immediately** — single-use, so a crash later in this session (or a
863
- * second rehydrate without an intervening clean close) is detected at the next open
864
- * and every adopt falls back to the always-correct drop+refill, self-healing any
865
- * crash-window divergence (coordinated commit is not 2PC across stores). The marker
866
- * payload is the JSON set of MVs that were **stale-at-close** (row-time maintenance
867
- * detached, so the durable backing may be behind); phase 3 withholds trust per-entry
868
- * for those — `trustBackings: trusted && !staleAtClose.has(name)` — so a
869
- * stale-at-close MV refills (recomputing content and re-arming maintenance) while
870
- * every live-at-close MV keeps the fast path. The one shared `adoptedBackings` set
871
- * composes across fixpoint rounds (an upstream MV adopted in round 1 enables its
872
- * dependent in round 2, while a refilled — or stale-at-close — upstream is never
873
- * added to it, forcing dependents to refill).
874
- *
875
- * **MV-over-MV ordering** is handled by a fixpoint retry rather than a static topo
876
- * sort: an MV's resolved `sourceTables` are computed at import time, not serialized
877
- * in the DDL, so they are unavailable before import. Each round passes the names of
878
- * every OTHER still-pending MV entry as `pendingDerivations`; the engine defers any
879
- * entry whose body reads one (its source already pre-exists as a phase-1 plain
880
- * table, so the body would otherwise plan against content the upstream's own import
881
- * may be about to replace). The loop repeats while any MV makes progress — robust to
882
- * arbitrary nesting depth. A genuinely unbuildable MV — a missing (e.g. memory)
883
- * source, or an unresolvable cycle — makes no progress in a round and is recorded in
884
- * `errors`.
885
- *
886
- * Per-entry errors in any phase are collected (not fatal) so one bad object does not
887
- * abort the rest.
888
- *
889
- * Call after `db.registerModule()` (and `db.setDefaultVtabName()` if DDL may lack a
890
- * USING clause).
891
- */
892
- rehydrateCatalog(db: Database): Promise<RehydrationResult>;
893
- /**
894
- * Consume the clean-shutdown marker, deleting it in the same breath (single-use).
895
- *
896
- * Returns `{ trusted, staleAtClose }`:
897
- * - `trusted` — the marker was present AND its payload parsed as a string array.
898
- * Absence (a fresh store, a crash, or a prior rehydrate without an intervening
899
- * {@link closeAll}) yields `false`, so every adopt this session falls back to
900
- * refill.
901
- * - `staleAtClose` — the qualified lowercased `schema.mv` names that were
902
- * stale-at-close (written by {@link closeAll}); those MVs refill even under trust.
903
- *
904
- * Conservative parse: any unparseable / wrong-shape payload (including a legacy
905
- * bare `'1'`, which parses to a number, not an array) degrades to
906
- * `{ trusted: false, staleAtClose: ∅ }` — refill everything — rather than
907
- * trust-everything. The marker is deleted regardless of parse outcome.
908
- */
909
- private consumeCleanShutdownMarker;
910
- /**
911
- * Remove DDL from the catalog store when a table is dropped.
912
- */
913
- removeTableDDL(schemaName: string, tableName: string): Promise<void>;
914
- /**
915
- * Persist a plain view's catalog entry (DDL via `generateViewDDL`), keyed by its
916
- * reserved view prefix. Compare-write (skip identical) — see
917
- * {@link persistObjectCatalogEntryIfChanged}.
918
- */
919
- saveViewDDL(view: ViewSchema): Promise<void>;
920
- /** Remove a plain view's catalog entry (on DROP VIEW). */
921
- removeViewDDL(schemaName: string, viewName: string): Promise<void>;
922
- /**
923
- * Persist a materialized view's catalog entry (DDL via `generateMaintainedTableDDL`
924
- * over the maintained table — the unified record), keyed by its reserved MV
925
- * prefix. Compare-write (skip identical) — see
926
- * {@link persistObjectCatalogEntryIfChanged}.
927
- */
928
- saveMaterializedViewDDL(mv: MaintainedTableSchema): Promise<void>;
929
- /** Remove a materialized view's catalog entry (on DROP MATERIALIZED VIEW). */
930
- removeMaterializedViewDDL(schemaName: string, mvName: string): Promise<void>;
931
- /**
932
- * Pre-flight veto (see `VirtualTableModule.assertCatalogObjectPersistable`): refuse a
933
- * view / materialized view whose catalog entry this module could not durably write.
934
- *
935
- * It runs exactly the derivation the write path runs — {@link viewCatalogEntry} /
936
- * {@link maintainedViewCatalogEntry} build the key (rejecting an unencodable
937
- * identifier) and {@link assertPersistableDdlText} checks the generated DDL text —
938
- * so the veto and the write cannot disagree about what is persistable. It is the only
939
- * synchronous path a rejection can travel: the actual save is chained onto
940
- * `persistQueue` behind a `SchemaChangeNotifier` listener, and both layers swallow.
941
233
  *
942
- * Gated on `subscribedDb === db`: this module persists a view/MV only while subscribed
943
- * to that `Database`'s change notifier ({@link ensureSchemaSubscription}, driven by the
944
- * first `create`/`connect`/`alterTable`/`rehydrateCatalog`). A module registered but
945
- * never handed a `db` writes nothing, so vetoing there would reject a definition that
946
- * loses nothing. The gate becomes removable if persistence is ever made unconditional
947
- * (`bug-store-untouched-table-and-early-view-never-persisted`).
234
+ * The request is sized from the table's own maintained row count when the planner
235
+ * supplied no estimate — see {@link sizeRequestFromLiveCount}.
948
236
  */
949
- assertCatalogObjectPersistable(db: Database, kind: CatalogObjectKind, object: ViewSchema | TableSchema): void;
950
- /**
951
- * Compare-write a view/MV catalog entry: write only when the entry is absent or its
952
- * DDL differs from `newDDL` (skip identical). Unlike the table path's
953
- * {@link persistCatalogIfChanged} there is **no** absent→skip self-filter — a view/MV
954
- * belongs to this db's catalog unconditionally (one module instance serves one
955
- * Database). Skipping identical writes makes any event whose regenerated DDL is
956
- * unchanged (e.g. a `materialized_view_refreshed`) a no-op, so a second consecutive
957
- * reopen yields identical catalog bytes. (Rehydration itself fires no view/MV events
958
- * at all — `importCatalog` is silent.)
959
- */
960
- private persistObjectCatalogEntryIfChanged;
961
- /**
962
- * Parse module configuration from vtab args.
963
- */
964
- private parseConfig;
965
- /**
966
- * Subscribe (once) to the engine's `SchemaChangeNotifier` so catalog-only
967
- * mutations that bypass `module.alterTable` — notably `ALTER … SET TAGS` and the
968
- * programmatic `setTableTags`/`setColumnTags`/`setConstraintTags` — still re-persist
969
- * the table's catalog DDL. Called lazily from the first `create`/`connect`/
970
- * `alterTable` hook that hands us a `db`.
971
- *
972
- * One `StoreModule` instance is assumed to serve one `Database`. A later hook
973
- * carrying a *different* `db` keeps the existing subscription (multi-database
974
- * sharing of a single module instance is out of scope) and logs.
975
- */
976
- private ensureSchemaSubscription;
977
- /**
978
- * Engine schema-change listener. Persists the catalog incrementally for the events
979
- * that bypass `module.alterTable` / `module.destroy`:
980
- *
981
- * - `table_modified` — every catalog-only tag swap (and the redundant follow-up a
982
- * structural ALTER fires). Keeps a connected `StoreTable`'s cached schema consistent
983
- * (SET TAGS does not call `updateSchema`) then read-compare-writes the table bundle.
984
- * - `view_added` / `view_modified` / `view_removed` — plain `CREATE`/`ALTER … SET TAGS`/
985
- * `DROP VIEW` (the engine fires these from the runtime emitters).
986
- * - `materialized_view_added` / `_modified` / `_refreshed` / `_removed` — MV lifecycle.
987
- * Like `table_modified`, the `_added`/`_modified`/`_refreshed` arms also synchronously
988
- * refresh the connected `StoreTable`'s cached schema so a tag change (e.g.
989
- * `quereus.sync.replicate`) takes effect immediately without reopen.
990
- *
991
- * Unlike the table path there is **no** catalog-absent self-filter for view/MV
992
- * add/remove: one `StoreModule` instance serves one `Database`, so that database's
993
- * views/MVs belong in its catalog unconditionally. A MEMORY-hosted maintained table
994
- * fires `table_added`/`table_removed`/`table_modified` like any table; those stay
995
- * ignored (`table_added`/`table_removed` fall through; its `table_modified` is
996
- * catalog-absent → skipped), so only the MV entry persists for it. A STORE-hosted
997
- * maintained table additionally persists its own table bundle through the ordinary
998
- * store-table machinery (which phase-1 rehydrate connects for the adopt fast path).
999
- *
1000
- * Synchronous by contract (`notifyChange` does not await listeners); every async write
1001
- * rides `persistQueue`, drained by `closeAll`/`whenCatalogPersisted`.
1002
- *
1003
- * After dispatching the event's own catalog persistence, the listener recomputes the
1004
- * durable stale-MV set and compare-writes it (see {@link persistStaleMvSetIfChanged}).
1005
- * Because the engine's MV manager subscribes to the same notifier in the `Database`
1006
- * constructor — before this lazy subscription — its listener runs first, so the
1007
- * `derivation.stale` flags are already current when this recompute reads them.
1008
- */
1009
- private onEngineSchemaChange;
1010
- /** Dispatch a single engine schema-change event to its catalog-persistence arm. */
1011
- private dispatchSchemaChange;
1012
- /**
1013
- * The qualified lowercased `schema.mv` names of every maintained table currently
1014
- * marked `derivation.stale` — an MV whose row-time maintenance detached mid-session
1015
- * (a body-relevant `table_modified`/`table_removed` on a source, or a cascade
1016
- * backing-invalidation), so its durable backing may be behind. The single source of
1017
- * truth for both the clean-shutdown marker payload and the durable stale-MV set.
1018
- *
1019
- * No subscribed db ⇒ the empty set: every path that can mark an MV stale requires a
1020
- * session in which this module observed the db (a store source create/connect or
1021
- * `rehydrateCatalog`, both of which subscribe), so a session without `subscribedDb`
1022
- * never detached any persisted MV's maintenance. Memory-backed MVs that appear here
1023
- * are harmless — their catalog entries always refill (no phase-1 pre-existing
1024
- * backing), so withholding trust from them is a no-op.
1025
- */
1026
- private computeStaleMvSet;
1027
- /**
1028
- * Recompute the durable stale-MV set and, only when it differs from the last value
1029
- * enqueued this subscription ({@link lastPersistedStaleMvs}), enqueue a `sync: true`
1030
- * point-write of it onto {@link persistQueue}. The compare-skip keeps an unrelated
1031
- * `table_modified` (a tag swap that changes no MV's staleness) from costing an fsync.
1032
- *
1033
- * The recompute reads `derivation.stale` synchronously (the flags are current at
1034
- * listener-dispatch time); the field is updated synchronously at enqueue time, and
1035
- * `persistQueue` serializes the writes in order, so the field always names the last
1036
- * enqueued value. Riding `persistQueue` (rather than a bare `put`) also serializes
1037
- * this `sync` behind the triggering event's own source-DDL compare-write, so the
1038
- * stale-set becomes durable no-later-than the source DDL that caused the staleness —
1039
- * the ordering the adopt soundness argument depends on.
1040
- */
1041
- private persistStaleMvSetIfChanged;
1042
- /**
1043
- * Write the durable stale-MV set (`\x00meta\x00stale_mvs`) with `sync: true`. Unlike
1044
- * the clean-shutdown marker this entry is persistent current-truth — overwritten as
1045
- * staleness changes, never deleted — so a crash leaves the last synced value intact.
1046
- * The `sync` mirrors the marker-consume delete's durability discipline (and carries
1047
- * the same documented backend caveat: a backend without a durability knob no-ops it).
1048
- */
1049
- private writeDurableStaleMvSet;
1050
- /**
1051
- * Read + conservative-parse the durable stale-MV set, mirroring
1052
- * {@link consumeCleanShutdownMarker}'s parse discipline (but READ-only — never
1053
- * deleted). Returns:
1054
- * - `{ present: false }` — no entry on disk (a pre-atomic store, or an upgrade where
1055
- * the capability was gained but no stale-set has been written yet) → the caller
1056
- * falls back to the clean-shutdown marker path.
1057
- * - `{ present: true, stale }` — the parsed set of qualified `schema.mv` names.
1058
- * - `{ present: true, stale: null }` — a present but unparseable / wrong-shape payload
1059
- * → the caller refills everything (the always-safe posture).
1060
- */
1061
- private readDurableStaleMvSet;
1062
- /**
1063
- * Shared MV add/modify/refresh handling. Narrow defensively — a derivation-less
1064
- * payload would be an engine bug, so skip. Otherwise, mirror `table_modified`:
1065
- * synchronously refresh a connected `StoreTable`'s cached schema (so a tag change
1066
- * such as `quereus.sync.replicate` takes effect immediately without reopen) before
1067
- * enqueuing the catalog DDL persist.
1068
- */
1069
- private refreshConnectedMaterializedView;
1070
- /**
1071
- * Append a catalog-persistence task to the serialized `persistQueue`, so successive
1072
- * mutations (e.g. SET TAGS (a=1) then SET TAGS ()) apply in order and are drained by
1073
- * `closeAll`/`whenCatalogPersisted` before the provider closes. Errors are
1074
- * swallowed+logged to mirror `notifyChange`'s own try/catch contract — a listener
1075
- * rejection must never escape.
1076
- */
1077
- private enqueuePersist;
1078
- /**
1079
- * Read-compare-write the catalog DDL for a table that just fired `table_modified`.
1080
- *
1081
- * - **Absent** catalog entry → the table is not store-backed in this catalog (a
1082
- * memory table in the same `db`, or a store table never persisted) → skip. This
1083
- * self-filters foreign-module tables without relying on `vtabModule` identity
1084
- * (which points at the isolation wrapper when wrapped).
1085
- * - **Present** but identical DDL → skip (no redundant write). This is what makes a
1086
- * structural ALTER — whose own `alterTable` already wrote the final DDL, then fires
1087
- * `table_modified` with that same final schema — a no-op here (no double-write).
1088
- * - **Present** and different DDL → re-persist (the tag swap; or a beneficial
1089
- * propagated rewrite of a dependent store table).
1090
- */
1091
- private persistCatalogIfChanged;
1092
- /**
1093
- * Resolve once all catalog writes queued by async schema-change listeners
1094
- * (catalog-only tag swaps) have settled. A durability barrier: `closeAll` awaits
1095
- * it internally; callers/tests that need the persisted catalog current without a
1096
- * full close can await it directly. Never rejects — queued errors are logged in
1097
- * the chain.
1098
- */
1099
- whenCatalogPersisted(): Promise<void>;
237
+ getBestAccessPlan(db: Database, tableInfo: TableSchema, request: BestAccessPlanRequest): BestAccessPlanResult;
1100
238
  /**
1101
239
  * Close all stores.
1102
240
  */
1103
241
  closeAll(): Promise<void>;
1104
- /**
1105
- * Get a table by schema and name.
1106
- */
1107
- getTable(schemaName: string, tableName: string): StoreTable | undefined;
1108
242
  }
1109
243
  //# sourceMappingURL=store-module.d.ts.map