@quereus/store 3.2.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +436 -317
- package/dist/src/common/backing-host.d.ts +198 -0
- package/dist/src/common/backing-host.d.ts.map +1 -0
- package/dist/src/common/backing-host.js +452 -0
- package/dist/src/common/backing-host.js.map +1 -0
- package/dist/src/common/bytes.d.ts +16 -0
- package/dist/src/common/bytes.d.ts.map +1 -0
- package/dist/src/common/bytes.js +33 -0
- package/dist/src/common/bytes.js.map +1 -0
- package/dist/src/common/cached-kv-store.d.ts +3 -3
- package/dist/src/common/cached-kv-store.d.ts.map +1 -1
- package/dist/src/common/cached-kv-store.js +6 -4
- package/dist/src/common/cached-kv-store.js.map +1 -1
- package/dist/src/common/encoding.d.ts +9 -1
- package/dist/src/common/encoding.d.ts.map +1 -1
- package/dist/src/common/encoding.js +12 -2
- package/dist/src/common/encoding.js.map +1 -1
- package/dist/src/common/index.d.ts +8 -6
- package/dist/src/common/index.d.ts.map +1 -1
- package/dist/src/common/index.js +7 -3
- package/dist/src/common/index.js.map +1 -1
- package/dist/src/common/key-builder.d.ts +125 -4
- package/dist/src/common/key-builder.d.ts.map +1 -1
- package/dist/src/common/key-builder.js +186 -9
- package/dist/src/common/key-builder.js.map +1 -1
- package/dist/src/common/kv-store.d.ts +75 -4
- package/dist/src/common/kv-store.d.ts.map +1 -1
- package/dist/src/common/memory-store.d.ts +3 -3
- package/dist/src/common/memory-store.d.ts.map +1 -1
- package/dist/src/common/memory-store.js +4 -2
- package/dist/src/common/memory-store.js.map +1 -1
- package/dist/src/common/store-connection.d.ts +11 -4
- package/dist/src/common/store-connection.d.ts.map +1 -1
- package/dist/src/common/store-connection.js +12 -6
- package/dist/src/common/store-connection.js.map +1 -1
- package/dist/src/common/store-module.d.ts +580 -20
- package/dist/src/common/store-module.d.ts.map +1 -1
- package/dist/src/common/store-module.js +1590 -135
- package/dist/src/common/store-module.js.map +1 -1
- package/dist/src/common/store-table.d.ts +347 -14
- package/dist/src/common/store-table.d.ts.map +1 -1
- package/dist/src/common/store-table.js +751 -98
- package/dist/src/common/store-table.js.map +1 -1
- package/dist/src/common/transaction.d.ts +135 -27
- package/dist/src/common/transaction.d.ts.map +1 -1
- package/dist/src/common/transaction.js +216 -55
- package/dist/src/common/transaction.js.map +1 -1
- package/package.json +5 -5
|
@@ -12,17 +12,22 @@
|
|
|
12
12
|
* - {prefix}.__stats__ - Unified stats store (row counts for all tables)
|
|
13
13
|
* - Catalog store: __catalog__ - DDL metadata keyed by {schema}.{table}
|
|
14
14
|
*/
|
|
15
|
-
import type { Database, TableSchema, TableIndexSchema, VirtualTableModule, BaseModuleConfig, BestAccessPlanRequest, BestAccessPlanResult, ModuleCapabilities, SchemaChangeInfo } from '@quereus/quereus';
|
|
15
|
+
import type { Database, TableSchema, TableIndexSchema, VirtualTableModule, BaseModuleConfig, BestAccessPlanRequest, BestAccessPlanResult, ModuleCapabilities, SchemaChangeInfo, Schema, MappingAdvertisement, ViewSchema, MaintainedTableSchema, BackingHost, LensDeploymentSnapshot } from '@quereus/quereus';
|
|
16
16
|
import type { KVStore, KVStoreProvider } from './kv-store.js';
|
|
17
17
|
import type { StoreEventEmitter } from './events.js';
|
|
18
18
|
import { TransactionCoordinator } from './transaction.js';
|
|
19
19
|
import { StoreTable, type StoreTableConfig, type StoreTableModule } from './store-table.js';
|
|
20
20
|
/**
|
|
21
21
|
* Result of catalog rehydration.
|
|
22
|
+
*
|
|
23
|
+
* `views` / `materializedViews` are additive (existing consumers — e.g.
|
|
24
|
+
* `quoomb-web` — read only `.errors`). Errors from any phase land in `errors`.
|
|
22
25
|
*/
|
|
23
26
|
export interface RehydrationResult {
|
|
24
27
|
tables: string[];
|
|
25
28
|
indexes: string[];
|
|
29
|
+
views: string[];
|
|
30
|
+
materializedViews: string[];
|
|
26
31
|
errors: RehydrationError[];
|
|
27
32
|
}
|
|
28
33
|
/**
|
|
@@ -32,6 +37,14 @@ export interface RehydrationError {
|
|
|
32
37
|
ddl: string;
|
|
33
38
|
error: Error;
|
|
34
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Listener the host binds via {@link StoreModule.setLensDeploymentListener} to
|
|
42
|
+
* forward a logical `apply schema` lens deployment onward (typically to the sync
|
|
43
|
+
* layer's basis-table lifecycle bookkeeping). Kept as a plain callback so
|
|
44
|
+
* `@quereus/store` stays free of a `@quereus/sync` dependency; the worker — which
|
|
45
|
+
* depends on both — wires the two together.
|
|
46
|
+
*/
|
|
47
|
+
export type LensDeploymentListener = (db: Database, logicalSchemaName: string, snapshot: LensDeploymentSnapshot) => void | Promise<void>;
|
|
35
48
|
/**
|
|
36
49
|
* Configuration options for StoreModule tables.
|
|
37
50
|
*/
|
|
@@ -57,16 +70,69 @@ export interface StoreModuleConfig extends BaseModuleConfig {
|
|
|
57
70
|
export declare class StoreModule implements VirtualTableModule<StoreTable, StoreModuleConfig>, StoreTableModule {
|
|
58
71
|
private provider;
|
|
59
72
|
private stores;
|
|
60
|
-
|
|
73
|
+
/**
|
|
74
|
+
* The single transaction coordinator shared by every {@link StoreTable} this
|
|
75
|
+
* module owns — the unit of cross-table atomicity (see {@link getCoordinator}).
|
|
76
|
+
* Lives for the module's lifetime: a single table's drop must NOT evict it
|
|
77
|
+
* (sibling tables still use it); only {@link closeAll} clears it.
|
|
78
|
+
*/
|
|
79
|
+
private moduleCoordinator?;
|
|
61
80
|
private tables;
|
|
62
81
|
private eventEmitter?;
|
|
82
|
+
/**
|
|
83
|
+
* Optional listener wired by the host (the worker) to forward a logical
|
|
84
|
+
* `apply schema` lens deployment to the sync layer's lifecycle bookkeeping.
|
|
85
|
+
* The store module is the only basis-backing host with both persistence and a
|
|
86
|
+
* `db` handle, so it is the forwarder; `@quereus/store` must not depend on
|
|
87
|
+
* `@quereus/sync`, so the listener is a plain callback the worker binds.
|
|
88
|
+
*/
|
|
89
|
+
private lensDeploymentListener?;
|
|
90
|
+
/** Unsubscribe thunk for the engine `SchemaChangeNotifier` listener, set on first hook with a `db`. */
|
|
91
|
+
private schemaListenerUnsub?;
|
|
92
|
+
/** The `Database` whose notifier we subscribed to. One module instance serves one `Database`. */
|
|
93
|
+
private subscribedDb?;
|
|
94
|
+
/**
|
|
95
|
+
* Serialized chain of pending catalog writes triggered by engine schema-change
|
|
96
|
+
* events (catalog-only tag swaps). `notifyChange` invokes listeners synchronously
|
|
97
|
+
* and does not await them, so the actual read-compare-write runs here, in order,
|
|
98
|
+
* and is drained by `closeAll` (and `whenCatalogPersisted`) before the provider closes.
|
|
99
|
+
*/
|
|
100
|
+
private persistQueue;
|
|
101
|
+
/**
|
|
102
|
+
* The JSON-serialized stale-MV set last enqueued onto {@link persistQueue} (the
|
|
103
|
+
* `\x00meta\x00stale_mvs` value), or `undefined` when no baseline is established
|
|
104
|
+
* for the current subscription. The schema-change listener compares each
|
|
105
|
+
* recomputed set against this so an unrelated `table_modified` (e.g. a tag swap
|
|
106
|
+
* that changes no MV's staleness) costs no extra fsync. Reset to `undefined`
|
|
107
|
+
* whenever a fresh `Database` is subscribed ({@link ensureSchemaSubscription}) so
|
|
108
|
+
* a reopened module re-establishes the baseline from the first relevant event (or
|
|
109
|
+
* the rehydrate tail).
|
|
110
|
+
*/
|
|
111
|
+
private lastPersistedStaleMvs;
|
|
112
|
+
/**
|
|
113
|
+
* Whether the provider exposes an atomic cross-store commit domain
|
|
114
|
+
* ({@link KVStoreProvider.beginAtomicBatch}) — the capability the MV adopt fast
|
|
115
|
+
* path's gate-5 drop hinges on. Fixed for the provider's lifetime, so cached at
|
|
116
|
+
* construction (and used as the single gate for both writing AND reading the durable
|
|
117
|
+
* stale-MV set). Gating the **write** on it is load-bearing for soundness, not just an
|
|
118
|
+
* fsync saving: only an atomic-capable session — whose commits can never tear a source
|
|
119
|
+
* from its same-module backing — ever writes the durable set, so the set a later atomic
|
|
120
|
+
* session reads was necessarily written under that same no-tear guarantee. A persistent
|
|
121
|
+
* non-atomic provider therefore can never leave a torn-but-"not-stale" set for an atomic
|
|
122
|
+
* reopen to trust (it writes none); such a store falls back to the clean-shutdown marker.
|
|
123
|
+
*/
|
|
124
|
+
private readonly atomicProvider;
|
|
63
125
|
constructor(provider: KVStoreProvider, eventEmitter?: StoreEventEmitter);
|
|
64
126
|
/**
|
|
65
127
|
* Returns capability flags for this module.
|
|
66
128
|
*
|
|
67
|
-
* The base StoreModule does NOT provide transaction isolation
|
|
68
|
-
*
|
|
69
|
-
*
|
|
129
|
+
* The base StoreModule does NOT provide transaction isolation: there is no
|
|
130
|
+
* snapshot isolation and no cross-connection isolation (readers on other
|
|
131
|
+
* connections see only committed data). Within a transaction, reads through
|
|
132
|
+
* the table's shared coordinator DO see that transaction's own pending
|
|
133
|
+
* writes (read-your-own-writes — `StoreTable.query` merges the pending op
|
|
134
|
+
* view over the committed store). For full isolation, wrap with
|
|
135
|
+
* IsolationModule:
|
|
70
136
|
*
|
|
71
137
|
* ```typescript
|
|
72
138
|
* import { IsolationModule, MemoryTableModule } from '@quereus/quereus';
|
|
@@ -81,14 +147,132 @@ export declare class StoreModule implements VirtualTableModule<StoreTable, Store
|
|
|
81
147
|
* ```
|
|
82
148
|
*/
|
|
83
149
|
getCapabilities(): ModuleCapabilities;
|
|
150
|
+
/**
|
|
151
|
+
* Generic-module mapping advertisements: assembled from the `quereus.lens.decomp.*`
|
|
152
|
+
* reserved tags on this basis schema's tables. Returns `[]` when the schema has no
|
|
153
|
+
* such tags, leaving the lens default mapper on its name-match path.
|
|
154
|
+
* See `docs/lens.md` § The Default Mapper.
|
|
155
|
+
*/
|
|
156
|
+
getMappingAdvertisements(_db: Database, basisSchema: Schema): readonly MappingAdvertisement[];
|
|
157
|
+
/**
|
|
158
|
+
* Backing-host capability (engine `vtab/backing-host.ts`): resolve the
|
|
159
|
+
* privileged surface for a store table this module owns, or undefined when the
|
|
160
|
+
* table is unknown to it. The host binds the CURRENT (StoreTable, coordinator)
|
|
161
|
+
* pair — `destroy` evicts both maps, so a drop+recreate yields fresh instances
|
|
162
|
+
* and the returned host is pinned to one backing-table incarnation (the engine
|
|
163
|
+
* resolves hosts fresh per call, never caching them). Resolution goes through
|
|
164
|
+
* {@link getOrReconnectTable} so a rehydrated-but-untouched (or rename-evicted)
|
|
165
|
+
* backing still resolves; the ownership pre-check keeps the reconnect fallback
|
|
166
|
+
* from adopting a registered table owned by a different module (`vtabModule`
|
|
167
|
+
* must be this StoreModule, or a wrapper — IsolationModule — exposing it as
|
|
168
|
+
* `underlying`). Attaching the coordinator eagerly makes the shared
|
|
169
|
+
* StoreTable's read paths merge the host's pending writes (reads-own-writes
|
|
170
|
+
* for a `select` from the MV mid-transaction).
|
|
171
|
+
*/
|
|
172
|
+
getBackingHost(db: Database, schemaName: string, tableName: string): BackingHost | undefined;
|
|
173
|
+
/**
|
|
174
|
+
* Resolve a {@link StoreTable} this module owns, reconnecting a
|
|
175
|
+
* rehydrated-but-untouched (or rename-evicted) table via
|
|
176
|
+
* {@link getOrReconnectTable}. The ownership pre-check keeps the reconnect
|
|
177
|
+
* fallback from adopting a registered table owned by a DIFFERENT module:
|
|
178
|
+
* `vtabModule` must be this StoreModule, or a wrapper (IsolationModule)
|
|
179
|
+
* exposing it as `underlying`. Returns undefined for an unknown/non-owned
|
|
180
|
+
* table. Shared by {@link getBackingHost} and
|
|
181
|
+
* {@link getTableForExternalWrite}; neither attaches a coordinator here —
|
|
182
|
+
* each layers its own pending-state policy on top.
|
|
183
|
+
*/
|
|
184
|
+
private resolveOwnedTable;
|
|
185
|
+
/**
|
|
186
|
+
* Resolve the live {@link StoreTable} for an externally-applied write to a
|
|
187
|
+
* SOURCE table (committed put/delete + secondary-index + stats maintenance via
|
|
188
|
+
* {@link StoreTable.applyExternalRowChanges}). Returns undefined when the table
|
|
189
|
+
* is not this module's.
|
|
190
|
+
*
|
|
191
|
+
* Resolution goes through {@link resolveOwnedTable} (the shared ownership
|
|
192
|
+
* pre-check + reconnect fallback). Unlike `getBackingHost` it attaches no
|
|
193
|
+
* coordinator: external writes target committed storage, and the before-image
|
|
194
|
+
* read (`readEffectiveRowByKey`) merges any already-attached coordinator's
|
|
195
|
+
* pending state on its own (none when the table is freshly reconnected).
|
|
196
|
+
*/
|
|
197
|
+
getTableForExternalWrite(db: Database, schemaName: string, tableName: string): StoreTable | undefined;
|
|
84
198
|
/**
|
|
85
199
|
* Get the event emitter for this module.
|
|
86
200
|
*/
|
|
87
201
|
getEventEmitter(): StoreEventEmitter | undefined;
|
|
202
|
+
/**
|
|
203
|
+
* Bind (or clear, with `undefined`) the lens-deployment forwarder. The host
|
|
204
|
+
* (worker) calls this to route a logical `apply schema`'s deployment to the
|
|
205
|
+
* sync layer; see {@link notifyLensDeployment}.
|
|
206
|
+
*/
|
|
207
|
+
setLensDeploymentListener(listener: LensDeploymentListener | undefined): void;
|
|
208
|
+
/**
|
|
209
|
+
* Engine `notifyLensDeployment` hook: a logical `apply schema X` fires this on
|
|
210
|
+
* every registered module once the lens catalog mutation + snapshot rotation
|
|
211
|
+
* complete (see `VirtualTableModule.notifyLensDeployment`). The store module —
|
|
212
|
+
* the basis-backing host — forwards the deployment to the bound listener so the
|
|
213
|
+
* sync layer can update its basis-table lifecycle bookkeeping.
|
|
214
|
+
*
|
|
215
|
+
* INVERSION OF THE ENGINE FIRING CONTRACT: the engine documents that a throwing
|
|
216
|
+
* notification aborts `apply schema X`. We deliberately wrap the listener in
|
|
217
|
+
* try/catch and SWALLOW (structured-log only) here — lifecycle bookkeeping is
|
|
218
|
+
* advisory and must never brick a schema apply. A bookkeeping bug is a logged
|
|
219
|
+
* warning, not a failed deploy.
|
|
220
|
+
*/
|
|
221
|
+
notifyLensDeployment(db: Database, logicalSchemaName: string, snapshot: LensDeploymentSnapshot): Promise<void>;
|
|
88
222
|
/**
|
|
89
223
|
* Get the KVStoreProvider used by this module.
|
|
90
224
|
*/
|
|
91
225
|
getProvider(): KVStoreProvider;
|
|
226
|
+
/**
|
|
227
|
+
* Build the set of physical store names this module's data/index stores
|
|
228
|
+
* currently occupy in `schemaName`, mapping each name to a human description
|
|
229
|
+
* of the logical object that owns it (for sited collision messages).
|
|
230
|
+
*
|
|
231
|
+
* Physical store names are built by string concatenation (`{schema}.{table}` /
|
|
232
|
+
* `{schema}.{table}_idx_{index}`) and the `_idx_` delimiter is itself a legal
|
|
233
|
+
* substring of any identifier, so two distinct logical objects (e.g. index
|
|
234
|
+
* `archive` on `t` and a sibling table `t_idx_archive`) can collapse to the
|
|
235
|
+
* same physical name. This set is the authoritative occupancy used by
|
|
236
|
+
* {@link assertStoreNameFree} to reject such a collision at CREATE time.
|
|
237
|
+
*
|
|
238
|
+
* The occupancy is the union of two sources, robust to lazy connection and the
|
|
239
|
+
* isolation wrapper (see the ticket's "Enumeration source" section):
|
|
240
|
+
* 1. `this.tables` — every store table this module touched this session.
|
|
241
|
+
* 2. the target schema's catalog tables whose `vtabModule === this` and which
|
|
242
|
+
* are not views — store-backed tables not yet lazily connected.
|
|
243
|
+
* Names embed the schema prefix, so cross-schema entries never collide and no
|
|
244
|
+
* per-schema filter on `this.tables` is needed. Memory-backed siblings and
|
|
245
|
+
* views own no store in this provider and are excluded (the `=== this` /
|
|
246
|
+
* `!isView` filter) to avoid false-positive rejects.
|
|
247
|
+
*
|
|
248
|
+
* No self-exclusion: at each guarded call the candidate object is not yet
|
|
249
|
+
* registered (create/createIndex run before the engine adds it) so it cannot
|
|
250
|
+
* self-collide; and for renameTable the renamed table's OWN stores stay in the
|
|
251
|
+
* set deliberately. Any overlap between a name the rename introduces and an
|
|
252
|
+
* own current store is a footprint-swap rename (`t` with index `x` → `t_idx_x`,
|
|
253
|
+
* or table `u_idx_x` with index `x` → `u`) that providers cannot relocate
|
|
254
|
+
* safely — relocation order determines whether a source is clobbered before it
|
|
255
|
+
* is moved — while no benign rename produces such an overlap. Keeping own
|
|
256
|
+
* stores in the set therefore causes no false rejects and keeps the
|
|
257
|
+
* reject-before-any-side-effect guarantee uniform.
|
|
258
|
+
*/
|
|
259
|
+
private collectOccupiedStoreNames;
|
|
260
|
+
/**
|
|
261
|
+
* Throws `StatusCode.ERROR` when `candidate` (a physical store name produced by
|
|
262
|
+
* the key-builder for an object about to be created/renamed) already names an
|
|
263
|
+
* existing data or index store in `schemaName`. `candidateDesc` describes the
|
|
264
|
+
* incoming logical object; the message names the candidate physical store and
|
|
265
|
+
* both conflicting logical objects and is actionable (rename one of them).
|
|
266
|
+
*
|
|
267
|
+
* Must run BEFORE any storage side-effect (`getStore` / `getIndexStore` / the
|
|
268
|
+
* physical relocation): the provider opens/creates the store eagerly, so a
|
|
269
|
+
* guard that ran after would already have aliased the colliding object's store.
|
|
270
|
+
*
|
|
271
|
+
* Callers checking several candidates against the same occupancy (renameTable
|
|
272
|
+
* checks the new data store plus every relocated index store) pass a
|
|
273
|
+
* precomputed `occupied` map so the occupancy is collected once, not per call.
|
|
274
|
+
*/
|
|
275
|
+
private assertStoreNameFree;
|
|
92
276
|
/**
|
|
93
277
|
* Creates a new store-backed table.
|
|
94
278
|
* Called by CREATE TABLE.
|
|
@@ -106,11 +290,59 @@ export declare class StoreModule implements VirtualTableModule<StoreTable, Store
|
|
|
106
290
|
/**
|
|
107
291
|
* Destroys a store table and its storage.
|
|
108
292
|
*/
|
|
109
|
-
destroy(
|
|
293
|
+
destroy(db: Database, _pAux: unknown, _moduleName: string, schemaName: string, tableName: string): Promise<void>;
|
|
294
|
+
/**
|
|
295
|
+
* Reclaim the local storage of a DETACHED basis table by name — the store-side
|
|
296
|
+
* target of the sync layer's basis-eviction sweep
|
|
297
|
+
* (`SyncManager.evictExpiredBasisTables`, `docs/migration.md` § 4 Contract).
|
|
298
|
+
*
|
|
299
|
+
* Unlike {@link destroy}, the table is no longer in the engine schema (it was
|
|
300
|
+
* removed from the basis on detach; only its physical storage lingered), so
|
|
301
|
+
* there is no `db`/schema to consult and NO schema-change event is emitted — the
|
|
302
|
+
* engine already saw the detach. The caller (the sync recorder) supplies the
|
|
303
|
+
* captured secondary-index name list it retained from before detach, because the
|
|
304
|
+
* table schema (and its index list) is gone: passing the exact names avoids the
|
|
305
|
+
* provider prefix-scanning `{table}_idx_`, which can clobber a sibling table
|
|
306
|
+
* literally named `{table}_idx_<x>`.
|
|
307
|
+
*
|
|
308
|
+
* Idempotent: any cached handles are evicted and disconnected, then the data /
|
|
309
|
+
* index / stats stores and the catalog DDL are removed. Storage already gone (a
|
|
310
|
+
* prior real `drop table` ran `destroy`) is treated as success — the provider's
|
|
311
|
+
* `deleteTableStores` no-ops on absent stores and `removeTableDDL` no-ops on an
|
|
312
|
+
* absent key.
|
|
313
|
+
*/
|
|
314
|
+
reclaimDetachedTable(schemaName: string, tableName: string, indexNames: readonly string[]): Promise<void>;
|
|
315
|
+
/**
|
|
316
|
+
* Tear down a table's in-memory handles and physical storage: evict the cached
|
|
317
|
+
* StoreTable / store / coordinator (synchronously, before any await, so a
|
|
318
|
+
* concurrent reconnect cannot observe a stale instance mid-teardown), disconnect
|
|
319
|
+
* the handle, delete the provider's data / index / stats stores (by the exact
|
|
320
|
+
* index names — never a `{table}_idx_` prefix scan, which could clobber a sibling
|
|
321
|
+
* table literally named `{table}_idx_<x>`), and remove the catalog DDL.
|
|
322
|
+
*
|
|
323
|
+
* Shared by {@link destroy} (live `drop table`) and {@link reclaimDetachedTable}
|
|
324
|
+
* (post-detach eviction). The caller owns the index-name source (live schema vs.
|
|
325
|
+
* the captured pre-detach list) and any schema-change event. Idempotent: the
|
|
326
|
+
* provider no-ops on absent stores and `removeTableDDL` no-ops on an absent key.
|
|
327
|
+
*/
|
|
328
|
+
private tearDownTableStorage;
|
|
329
|
+
/**
|
|
330
|
+
* Returns the connected StoreTable for `schemaName.tableName`, lazily
|
|
331
|
+
* reconnecting from the engine's schema registry when absent.
|
|
332
|
+
*
|
|
333
|
+
* `renameTable` evicts the old key from `this.tables` and expects the next
|
|
334
|
+
* `connect()` to repopulate under the new name, but `apply schema` can run
|
|
335
|
+
* follow-up DDL (ALTER TABLE, CREATE/DROP INDEX) against the new name
|
|
336
|
+
* without an intervening connect. Mirror connect()'s schemaManager lookup
|
|
337
|
+
* so that DDL finds the moved table. Safe for the index paths because
|
|
338
|
+
* SchemaManager calls the module BEFORE mutating the registered table
|
|
339
|
+
* schema, so the reconnected cache matches what a connected instance holds.
|
|
340
|
+
*/
|
|
341
|
+
private getOrReconnectTable;
|
|
110
342
|
/**
|
|
111
343
|
* Creates an index on a store-backed table.
|
|
112
344
|
*/
|
|
113
|
-
createIndex(
|
|
345
|
+
createIndex(db: Database, schemaName: string, tableName: string, indexSchema: TableIndexSchema): Promise<void>;
|
|
114
346
|
/**
|
|
115
347
|
* Drops an index on a store-backed table.
|
|
116
348
|
*
|
|
@@ -119,7 +351,7 @@ export declare class StoreModule implements VirtualTableModule<StoreTable, Store
|
|
|
119
351
|
* synthesized from it, tagged with `derivedFromIndex`), releases the
|
|
120
352
|
* cached index-store handle, and tears down the underlying index store.
|
|
121
353
|
*/
|
|
122
|
-
dropIndex(
|
|
354
|
+
dropIndex(db: Database, schemaName: string, tableName: string, indexName: string): Promise<void>;
|
|
123
355
|
/**
|
|
124
356
|
* Build index entries for all existing rows in a table.
|
|
125
357
|
*
|
|
@@ -130,6 +362,37 @@ export declare class StoreModule implements VirtualTableModule<StoreTable, Store
|
|
|
130
362
|
* atomically.
|
|
131
363
|
*/
|
|
132
364
|
private buildIndexEntries;
|
|
365
|
+
/**
|
|
366
|
+
* Clear and rebuild every secondary index of `schema` against the (already
|
|
367
|
+
* re-encoded) data store. Secondary-index keys embed the PK suffix, so they must
|
|
368
|
+
* be rewritten whenever the data-store PK key bytes change — which happens both
|
|
369
|
+
* on `ALTER PRIMARY KEY` (the PK columns change) and on an `ALTER COLUMN … SET
|
|
370
|
+
* COLLATE` on a PK member (a PK column's key collation changes). Shared by both
|
|
371
|
+
* arms so the clear-then-rebuild stays identical. `schema` must already be the
|
|
372
|
+
* post-ALTER schema (its `primaryKeyDefinition` + column collations drive the new
|
|
373
|
+
* PK-suffix encoding via {@link buildIndexEntries}).
|
|
374
|
+
*/
|
|
375
|
+
private rebuildSecondaryIndexes;
|
|
376
|
+
/**
|
|
377
|
+
* Validates the existing rows in `dataStore` against a UNIQUE constraint,
|
|
378
|
+
* throwing `CONSTRAINT` on the first duplicate before any schema mutation.
|
|
379
|
+
* Used by `ADD CONSTRAINT UNIQUE` (validate against the current collation) and
|
|
380
|
+
* by `SET COLLATE` (pass an `updatedSchema` whose altered column carries the
|
|
381
|
+
* NEW collation, so the dedup is performed under it). Mirrors the duplicate
|
|
382
|
+
* detection in {@link buildIndexEntries}: a `seen` Set keyed on a per-column
|
|
383
|
+
* collation-aware signature of the constrained values, with SQL NULL semantics
|
|
384
|
+
* (a row with any NULL constrained value never counts as a duplicate) and the
|
|
385
|
+
* partial `predicate` honored.
|
|
386
|
+
*
|
|
387
|
+
* No index store is written — store UNIQUE enforcement is a full-scan over
|
|
388
|
+
* `uniqueConstraints` at write time. The signature is built by
|
|
389
|
+
* {@link serializeRowKey} with one normalizer per constrained column drawn from
|
|
390
|
+
* `tableSchema.columns[idx].collation`, so a per-column NOCASE/RTRIM collation
|
|
391
|
+
* is honored (matching write-time `compareSqlValues` enforcement). Residual: a
|
|
392
|
+
* custom comparator-only collation has no string normalizer and falls back to
|
|
393
|
+
* BINARY for the dedup (see docs/schema.md store-collation note).
|
|
394
|
+
*/
|
|
395
|
+
private validateUniqueOverExistingRows;
|
|
133
396
|
/**
|
|
134
397
|
* Alters an existing store table's structure (ADD/DROP/RENAME COLUMN).
|
|
135
398
|
* Performs eager row migration for ADD and DROP, schema-only update for RENAME.
|
|
@@ -149,8 +412,23 @@ export declare class StoreModule implements VirtualTableModule<StoreTable, Store
|
|
|
149
412
|
renameTable(db: Database, schemaName: string, oldName: string, newName: string): Promise<void>;
|
|
150
413
|
/**
|
|
151
414
|
* Modern access planning interface.
|
|
415
|
+
*
|
|
416
|
+
* Every plan is stamped with `honorsCollatedRangeBounds`: the store's post-fetch
|
|
417
|
+
* row filter (`StoreTable.matchesFilters` → `compareValues`) compares each pushed
|
|
418
|
+
* constraint — including LT/LE/GT/GE range bounds — under the column's declared
|
|
419
|
+
* collation, so the access path's collation-cover analysis may keep a
|
|
420
|
+
* collation-matched non-BINARY (NOCASE/RTRIM) PK range/BETWEEN seek instead of
|
|
421
|
+
* declining to a SeqScan + residual (see `classifyConstraintCover` in
|
|
422
|
+
* rule-select-access-path.ts). The seek really does narrow: `StoreTable.scanPKRange`
|
|
423
|
+
* (via `StoreTable.buildPKRangeBounds`) encodes the LT/LE/GT/GE bounds under the
|
|
424
|
+
* same per-column key collations the data keys use and iterates that
|
|
425
|
+
* seek-start/early-termination window. The window is a SUPERSET, so the post-fetch
|
|
426
|
+
* row filter still reproduces the exact collation semantics — and a comparator-only
|
|
427
|
+
* collation with no byte encoder safely falls back to a full scan. Mirrors the
|
|
428
|
+
* memory module's advertisement.
|
|
152
429
|
*/
|
|
153
430
|
getBestAccessPlan(_db: Database, tableInfo: TableSchema, request: BestAccessPlanRequest): BestAccessPlanResult;
|
|
431
|
+
private computeBestAccessPlan;
|
|
154
432
|
/**
|
|
155
433
|
* Compute the PK-ordering advertisement for a scan-style plan. Returns the
|
|
156
434
|
* `providesOrdering` / `monotonicOn` / `supportsAsofRight` fields for a plan
|
|
@@ -189,38 +467,320 @@ export declare class StoreModule implements VirtualTableModule<StoreTable, Store
|
|
|
189
467
|
*/
|
|
190
468
|
getStatsStore(schemaName: string, tableName: string): Promise<KVStore>;
|
|
191
469
|
/**
|
|
192
|
-
* Get
|
|
470
|
+
* Get (lazily constructing) the SINGLE transaction coordinator shared by every
|
|
471
|
+
* table this module owns — the unit of cross-table atomicity. Every
|
|
472
|
+
* {@link StoreTable} attaches to the same instance, so a transaction touching
|
|
473
|
+
* several of the module's tables commits/rolls back as one all-or-nothing
|
|
474
|
+
* batch.
|
|
475
|
+
*
|
|
476
|
+
* Synchronously constructible: ops are addressed by explicit store handle, so
|
|
477
|
+
* the coordinator never needs to open a store to be created. This lets callers
|
|
478
|
+
* that must stay synchronous (e.g. a `BackingHost.connect()`) obtain a working
|
|
479
|
+
* coordinator before any table's store has been opened.
|
|
480
|
+
*/
|
|
481
|
+
getCoordinator(): TransactionCoordinator;
|
|
482
|
+
/**
|
|
483
|
+
* Build the catalog entry for a table: its CREATE TABLE DDL, one
|
|
484
|
+
* `CREATE [UNIQUE] INDEX` statement per persistable secondary index, and one
|
|
485
|
+
* `alter index … set tags (…)` statement per exposed implicit index carrying
|
|
486
|
+
* user tags, newline-joined into a single multi-statement bundle keyed by
|
|
487
|
+
* `{schema}.{table}`.
|
|
488
|
+
*
|
|
489
|
+
* Bundling the indexes into the table's own entry means every existing
|
|
490
|
+
* re-persist path — `saveTableDDL` (each `alterTable` arm, `renameTable`) and
|
|
491
|
+
* the `table_modified` listener (`persistCatalogIfChanged`) — carries the
|
|
492
|
+
* indexes along for free, and `removeTableDDL` drops them with the table. The
|
|
493
|
+
* bundle is consumed on reopen by `rehydrateCatalog` → `importCatalog`, whose
|
|
494
|
+
* `parser.parseAll` splits it AST-by-AST (never on `\n`) and imports each
|
|
495
|
+
* statement in document order, so the table registers before its indexes.
|
|
496
|
+
*
|
|
497
|
+
* Both the table DDL and the index DDL are emitted without a `db` arg, keeping
|
|
498
|
+
* the persistence-safe fully-qualified form. Hidden implicit covering indexes
|
|
499
|
+
* (the auto-built BTree backing a declared UNIQUE constraint) are excluded —
|
|
500
|
+
* they are a backing detail that round-trips via the table's UNIQUE constraint,
|
|
501
|
+
* not as a standalone CREATE INDEX. For store tables `buildTableSchemaFromAST`
|
|
502
|
+
* synthesizes none of those, so in practice every index is included; the guard
|
|
503
|
+
* is defensive. A `CREATE UNIQUE INDEX`'s derived UNIQUE constraint is already
|
|
504
|
+
* excluded from the table DDL by the generator, so it round-trips solely via
|
|
505
|
+
* its own `CREATE UNIQUE INDEX` line — no doubling.
|
|
506
|
+
*
|
|
507
|
+
* An *exposed* implicit index (a non-derived UNIQUE constraint tagged
|
|
508
|
+
* `quereus.expose_implicit_index = true`, never materialized in store mode)
|
|
509
|
+
* must NOT get a synthetic `CREATE INDEX` line — re-import would materialize a
|
|
510
|
+
* real `IndexSchema` and change the store-mode shape. Its user tags
|
|
511
|
+
* (`UniqueConstraintSchema.exposedIndexTags`) instead ride a trailing
|
|
512
|
+
* `alter index … set tags` line, which `importDDL` re-applies silently after
|
|
513
|
+
* the CREATE TABLE in the same entry has registered the constraint. Empty tag
|
|
514
|
+
* records emit no line, and `exposedImplicitIndexes` returns `[]` for
|
|
515
|
+
* memory-mode tables (name materialized) and orders descriptors by the
|
|
516
|
+
* `uniqueConstraints` array, so the bundle stays byte-deterministic — which
|
|
517
|
+
* the compare-write in `persistCatalogIfChanged` relies on.
|
|
193
518
|
*/
|
|
194
|
-
|
|
519
|
+
private buildCatalogEntry;
|
|
195
520
|
/**
|
|
196
|
-
* Save table DDL to the catalog store.
|
|
521
|
+
* Save table DDL (bundled with its secondary index DDL) to the catalog store.
|
|
197
522
|
*/
|
|
198
523
|
saveTableDDL(tableSchema: TableSchema): Promise<void>;
|
|
199
524
|
/**
|
|
200
|
-
* Load all DDL
|
|
201
|
-
* Used to restore persisted tables on startup.
|
|
525
|
+
* Load all DDL **values** from the catalog store (keys discarded).
|
|
526
|
+
* Used to restore persisted tables on startup and by tests asserting persisted DDL.
|
|
527
|
+
* Note: this returns table, view, AND materialized-view entries intermixed —
|
|
528
|
+
* {@link rehydrateCatalog} uses {@link loadCatalogEntries} (keys retained) to
|
|
529
|
+
* classify them. Meta entries (e.g. the clean-shutdown marker) are not DDL
|
|
530
|
+
* and are filtered out.
|
|
202
531
|
*/
|
|
203
532
|
loadAllDDL(): Promise<string[]>;
|
|
204
533
|
/**
|
|
205
|
-
*
|
|
534
|
+
* Load every catalog entry as `{ key, ddl }`. Unlike {@link loadAllDDL} (values
|
|
535
|
+
* only) this retains the key so {@link rehydrateCatalog} can classify each entry
|
|
536
|
+
* (table / view / materialized view) by its reserved key prefix.
|
|
537
|
+
*/
|
|
538
|
+
private loadCatalogEntries;
|
|
539
|
+
/**
|
|
540
|
+
* Rehydrate the persisted catalog into the in-memory schema manager, in
|
|
541
|
+
* dependency order.
|
|
542
|
+
*
|
|
543
|
+
* Establishes the engine schema-change subscription up front (so a reopened DB
|
|
544
|
+
* persists subsequent DDL even when its first post-reopen statement is a view/MV,
|
|
545
|
+
* which never routes through a module hook — all the lazy subscription points are
|
|
546
|
+
* table hooks). Then loads every catalog entry once, classifies each by its key
|
|
547
|
+
* prefix into {tables, views, materialized views}, and imports in three phases:
|
|
548
|
+
*
|
|
549
|
+
* 1. **Tables** — `importCatalog` (connect to existing storage; refresh connected
|
|
550
|
+
* `StoreTable` schemas).
|
|
551
|
+
* 2. **Views** — `importCatalog` (engine silent-register; body validation deferred
|
|
552
|
+
* to query time, so order among views — and view-over-MV / view-over-view —
|
|
553
|
+
* does not matter, and no schema-change event fires → phase 2 writes nothing).
|
|
554
|
+
* 3. **Materialized views** — `importCatalog` per entry (engine re-materialize:
|
|
555
|
+
* rebuilds the memory backing from current source data, re-registers row-time
|
|
556
|
+
* maintenance, re-runs the eligibility gate — the same core the create emitter
|
|
557
|
+
* uses, but silent: no `materialized_view_added` fires, so phase 3 writes
|
|
558
|
+
* nothing back to the catalog). A store-hosted backing that phase 1 already
|
|
559
|
+
* rehydrated is **adopted without the refill** when the engine's adopt gates
|
|
560
|
+
* pass — see the clean-shutdown marker below.
|
|
561
|
+
*
|
|
562
|
+
* **Clean-shutdown marker.** Before anything loads, the reserved
|
|
563
|
+
* `\x00meta\x00clean_shutdown` catalog entry (written by {@link closeAll} after
|
|
564
|
+
* every batch flushed) is consumed: parsed into `{ trusted, staleAtClose }`, then
|
|
565
|
+
* **deleted immediately** — single-use, so a crash later in this session (or a
|
|
566
|
+
* second rehydrate without an intervening clean close) is detected at the next open
|
|
567
|
+
* and every adopt falls back to the always-correct drop+refill, self-healing any
|
|
568
|
+
* crash-window divergence (coordinated commit is not 2PC across stores). The marker
|
|
569
|
+
* payload is the JSON set of MVs that were **stale-at-close** (row-time maintenance
|
|
570
|
+
* detached, so the durable backing may be behind); phase 3 withholds trust per-entry
|
|
571
|
+
* for those — `trustBackings: trusted && !staleAtClose.has(name)` — so a
|
|
572
|
+
* stale-at-close MV refills (recomputing content and re-arming maintenance) while
|
|
573
|
+
* every live-at-close MV keeps the fast path. The one shared `adoptedBackings` set
|
|
574
|
+
* composes across fixpoint rounds (an upstream MV adopted in round 1 enables its
|
|
575
|
+
* dependent in round 2, while a refilled — or stale-at-close — upstream is never
|
|
576
|
+
* added to it, forcing dependents to refill).
|
|
577
|
+
*
|
|
578
|
+
* **MV-over-MV ordering** is handled by a fixpoint retry rather than a static topo
|
|
579
|
+
* sort: an MV's resolved `sourceTables` are computed at import time, not serialized
|
|
580
|
+
* in the DDL, so they are unavailable before import. Each round passes the names of
|
|
581
|
+
* every OTHER still-pending MV entry as `pendingDerivations`; the engine defers any
|
|
582
|
+
* entry whose body reads one (its source already pre-exists as a phase-1 plain
|
|
583
|
+
* table, so the body would otherwise plan against content the upstream's own import
|
|
584
|
+
* may be about to replace). The loop repeats while any MV makes progress — robust to
|
|
585
|
+
* arbitrary nesting depth. A genuinely unbuildable MV — a missing (e.g. memory)
|
|
586
|
+
* source, or an unresolvable cycle — makes no progress in a round and is recorded in
|
|
587
|
+
* `errors`.
|
|
206
588
|
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* so a single corrupt entry does not prevent other tables from
|
|
210
|
-
* loading.
|
|
589
|
+
* Per-entry errors in any phase are collected (not fatal) so one bad object does not
|
|
590
|
+
* abort the rest.
|
|
211
591
|
*
|
|
212
|
-
* Call after `db.registerModule()` (and `db.setDefaultVtabName()`
|
|
213
|
-
*
|
|
592
|
+
* Call after `db.registerModule()` (and `db.setDefaultVtabName()` if DDL may lack a
|
|
593
|
+
* USING clause).
|
|
214
594
|
*/
|
|
215
595
|
rehydrateCatalog(db: Database): Promise<RehydrationResult>;
|
|
596
|
+
/**
|
|
597
|
+
* Consume the clean-shutdown marker, deleting it in the same breath (single-use).
|
|
598
|
+
*
|
|
599
|
+
* Returns `{ trusted, staleAtClose }`:
|
|
600
|
+
* - `trusted` — the marker was present AND its payload parsed as a string array.
|
|
601
|
+
* Absence (a fresh store, a crash, or a prior rehydrate without an intervening
|
|
602
|
+
* {@link closeAll}) yields `false`, so every adopt this session falls back to
|
|
603
|
+
* refill.
|
|
604
|
+
* - `staleAtClose` — the qualified lowercased `schema.mv` names that were
|
|
605
|
+
* stale-at-close (written by {@link closeAll}); those MVs refill even under trust.
|
|
606
|
+
*
|
|
607
|
+
* Conservative parse: any unparseable / wrong-shape payload (including a legacy
|
|
608
|
+
* bare `'1'`, which parses to a number, not an array) degrades to
|
|
609
|
+
* `{ trusted: false, staleAtClose: ∅ }` — refill everything — rather than
|
|
610
|
+
* trust-everything. The marker is deleted regardless of parse outcome.
|
|
611
|
+
*/
|
|
612
|
+
private consumeCleanShutdownMarker;
|
|
216
613
|
/**
|
|
217
614
|
* Remove DDL from the catalog store when a table is dropped.
|
|
218
615
|
*/
|
|
219
616
|
removeTableDDL(schemaName: string, tableName: string): Promise<void>;
|
|
617
|
+
/**
|
|
618
|
+
* Persist a plain view's catalog entry (DDL via `generateViewDDL`), keyed by its
|
|
619
|
+
* reserved view prefix. Compare-write (skip identical) — see
|
|
620
|
+
* {@link persistObjectCatalogEntryIfChanged}.
|
|
621
|
+
*/
|
|
622
|
+
saveViewDDL(view: ViewSchema): Promise<void>;
|
|
623
|
+
/** Remove a plain view's catalog entry (on DROP VIEW). */
|
|
624
|
+
removeViewDDL(schemaName: string, viewName: string): Promise<void>;
|
|
625
|
+
/**
|
|
626
|
+
* Persist a materialized view's catalog entry (DDL via `generateMaintainedTableDDL`
|
|
627
|
+
* over the maintained table — the unified record), keyed by its reserved MV
|
|
628
|
+
* prefix. Compare-write (skip identical) — see
|
|
629
|
+
* {@link persistObjectCatalogEntryIfChanged}.
|
|
630
|
+
*/
|
|
631
|
+
saveMaterializedViewDDL(mv: MaintainedTableSchema): Promise<void>;
|
|
632
|
+
/** Remove a materialized view's catalog entry (on DROP MATERIALIZED VIEW). */
|
|
633
|
+
removeMaterializedViewDDL(schemaName: string, mvName: string): Promise<void>;
|
|
634
|
+
/**
|
|
635
|
+
* Compare-write a view/MV catalog entry: write only when the entry is absent or its
|
|
636
|
+
* DDL differs from `newDDL` (skip identical). Unlike the table path's
|
|
637
|
+
* {@link persistCatalogIfChanged} there is **no** absent→skip self-filter — a view/MV
|
|
638
|
+
* belongs to this db's catalog unconditionally (one module instance serves one
|
|
639
|
+
* Database). Skipping identical writes makes any event whose regenerated DDL is
|
|
640
|
+
* unchanged (e.g. a `materialized_view_refreshed`) a no-op, so a second consecutive
|
|
641
|
+
* reopen yields identical catalog bytes. (Rehydration itself fires no view/MV events
|
|
642
|
+
* at all — `importCatalog` is silent.)
|
|
643
|
+
*/
|
|
644
|
+
private persistObjectCatalogEntryIfChanged;
|
|
220
645
|
/**
|
|
221
646
|
* Parse module configuration from vtab args.
|
|
222
647
|
*/
|
|
223
648
|
private parseConfig;
|
|
649
|
+
/**
|
|
650
|
+
* Subscribe (once) to the engine's `SchemaChangeNotifier` so catalog-only
|
|
651
|
+
* mutations that bypass `module.alterTable` — notably `ALTER … SET TAGS` and the
|
|
652
|
+
* programmatic `setTableTags`/`setColumnTags`/`setConstraintTags` — still re-persist
|
|
653
|
+
* the table's catalog DDL. Called lazily from the first `create`/`connect`/
|
|
654
|
+
* `alterTable` hook that hands us a `db`.
|
|
655
|
+
*
|
|
656
|
+
* One `StoreModule` instance is assumed to serve one `Database`. A later hook
|
|
657
|
+
* carrying a *different* `db` keeps the existing subscription (multi-database
|
|
658
|
+
* sharing of a single module instance is out of scope) and logs.
|
|
659
|
+
*/
|
|
660
|
+
private ensureSchemaSubscription;
|
|
661
|
+
/**
|
|
662
|
+
* Engine schema-change listener. Persists the catalog incrementally for the events
|
|
663
|
+
* that bypass `module.alterTable` / `module.destroy`:
|
|
664
|
+
*
|
|
665
|
+
* - `table_modified` — every catalog-only tag swap (and the redundant follow-up a
|
|
666
|
+
* structural ALTER fires). Keeps a connected `StoreTable`'s cached schema consistent
|
|
667
|
+
* (SET TAGS does not call `updateSchema`) then read-compare-writes the table bundle.
|
|
668
|
+
* - `view_added` / `view_modified` / `view_removed` — plain `CREATE`/`ALTER … SET TAGS`/
|
|
669
|
+
* `DROP VIEW` (the engine fires these from the runtime emitters).
|
|
670
|
+
* - `materialized_view_added` / `_modified` / `_refreshed` / `_removed` — MV lifecycle.
|
|
671
|
+
* Like `table_modified`, the `_added`/`_modified`/`_refreshed` arms also synchronously
|
|
672
|
+
* refresh the connected `StoreTable`'s cached schema so a tag change (e.g.
|
|
673
|
+
* `quereus.sync.replicate`) takes effect immediately without reopen.
|
|
674
|
+
*
|
|
675
|
+
* Unlike the table path there is **no** catalog-absent self-filter for view/MV
|
|
676
|
+
* add/remove: one `StoreModule` instance serves one `Database`, so that database's
|
|
677
|
+
* views/MVs belong in its catalog unconditionally. A MEMORY-hosted maintained table
|
|
678
|
+
* fires `table_added`/`table_removed`/`table_modified` like any table; those stay
|
|
679
|
+
* ignored (`table_added`/`table_removed` fall through; its `table_modified` is
|
|
680
|
+
* catalog-absent → skipped), so only the MV entry persists for it. A STORE-hosted
|
|
681
|
+
* maintained table additionally persists its own table bundle through the ordinary
|
|
682
|
+
* store-table machinery (which phase-1 rehydrate connects for the adopt fast path).
|
|
683
|
+
*
|
|
684
|
+
* Synchronous by contract (`notifyChange` does not await listeners); every async write
|
|
685
|
+
* rides `persistQueue`, drained by `closeAll`/`whenCatalogPersisted`.
|
|
686
|
+
*
|
|
687
|
+
* After dispatching the event's own catalog persistence, the listener recomputes the
|
|
688
|
+
* durable stale-MV set and compare-writes it (see {@link persistStaleMvSetIfChanged}).
|
|
689
|
+
* Because the engine's MV manager subscribes to the same notifier in the `Database`
|
|
690
|
+
* constructor — before this lazy subscription — its listener runs first, so the
|
|
691
|
+
* `derivation.stale` flags are already current when this recompute reads them.
|
|
692
|
+
*/
|
|
693
|
+
private onEngineSchemaChange;
|
|
694
|
+
/** Dispatch a single engine schema-change event to its catalog-persistence arm. */
|
|
695
|
+
private dispatchSchemaChange;
|
|
696
|
+
/**
|
|
697
|
+
* The qualified lowercased `schema.mv` names of every maintained table currently
|
|
698
|
+
* marked `derivation.stale` — an MV whose row-time maintenance detached mid-session
|
|
699
|
+
* (a body-relevant `table_modified`/`table_removed` on a source, or a cascade
|
|
700
|
+
* backing-invalidation), so its durable backing may be behind. The single source of
|
|
701
|
+
* truth for both the clean-shutdown marker payload and the durable stale-MV set.
|
|
702
|
+
*
|
|
703
|
+
* No subscribed db ⇒ the empty set: every path that can mark an MV stale requires a
|
|
704
|
+
* session in which this module observed the db (a store source create/connect or
|
|
705
|
+
* `rehydrateCatalog`, both of which subscribe), so a session without `subscribedDb`
|
|
706
|
+
* never detached any persisted MV's maintenance. Memory-backed MVs that appear here
|
|
707
|
+
* are harmless — their catalog entries always refill (no phase-1 pre-existing
|
|
708
|
+
* backing), so withholding trust from them is a no-op.
|
|
709
|
+
*/
|
|
710
|
+
private computeStaleMvSet;
|
|
711
|
+
/**
|
|
712
|
+
* Recompute the durable stale-MV set and, only when it differs from the last value
|
|
713
|
+
* enqueued this subscription ({@link lastPersistedStaleMvs}), enqueue a `sync: true`
|
|
714
|
+
* point-write of it onto {@link persistQueue}. The compare-skip keeps an unrelated
|
|
715
|
+
* `table_modified` (a tag swap that changes no MV's staleness) from costing an fsync.
|
|
716
|
+
*
|
|
717
|
+
* The recompute reads `derivation.stale` synchronously (the flags are current at
|
|
718
|
+
* listener-dispatch time); the field is updated synchronously at enqueue time, and
|
|
719
|
+
* `persistQueue` serializes the writes in order, so the field always names the last
|
|
720
|
+
* enqueued value. Riding `persistQueue` (rather than a bare `put`) also serializes
|
|
721
|
+
* this `sync` behind the triggering event's own source-DDL compare-write, so the
|
|
722
|
+
* stale-set becomes durable no-later-than the source DDL that caused the staleness —
|
|
723
|
+
* the ordering the adopt soundness argument depends on.
|
|
724
|
+
*/
|
|
725
|
+
private persistStaleMvSetIfChanged;
|
|
726
|
+
/**
|
|
727
|
+
* Write the durable stale-MV set (`\x00meta\x00stale_mvs`) with `sync: true`. Unlike
|
|
728
|
+
* the clean-shutdown marker this entry is persistent current-truth — overwritten as
|
|
729
|
+
* staleness changes, never deleted — so a crash leaves the last synced value intact.
|
|
730
|
+
* The `sync` mirrors the marker-consume delete's durability discipline (and carries
|
|
731
|
+
* the same documented backend caveat: a backend without a durability knob no-ops it).
|
|
732
|
+
*/
|
|
733
|
+
private writeDurableStaleMvSet;
|
|
734
|
+
/**
|
|
735
|
+
* Read + conservative-parse the durable stale-MV set, mirroring
|
|
736
|
+
* {@link consumeCleanShutdownMarker}'s parse discipline (but READ-only — never
|
|
737
|
+
* deleted). Returns:
|
|
738
|
+
* - `{ present: false }` — no entry on disk (a pre-atomic store, or an upgrade where
|
|
739
|
+
* the capability was gained but no stale-set has been written yet) → the caller
|
|
740
|
+
* falls back to the clean-shutdown marker path.
|
|
741
|
+
* - `{ present: true, stale }` — the parsed set of qualified `schema.mv` names.
|
|
742
|
+
* - `{ present: true, stale: null }` — a present but unparseable / wrong-shape payload
|
|
743
|
+
* → the caller refills everything (the always-safe posture).
|
|
744
|
+
*/
|
|
745
|
+
private readDurableStaleMvSet;
|
|
746
|
+
/**
|
|
747
|
+
* Shared MV add/modify/refresh handling. Narrow defensively — a derivation-less
|
|
748
|
+
* payload would be an engine bug, so skip. Otherwise, mirror `table_modified`:
|
|
749
|
+
* synchronously refresh a connected `StoreTable`'s cached schema (so a tag change
|
|
750
|
+
* such as `quereus.sync.replicate` takes effect immediately without reopen) before
|
|
751
|
+
* enqueuing the catalog DDL persist.
|
|
752
|
+
*/
|
|
753
|
+
private refreshConnectedMaterializedView;
|
|
754
|
+
/**
|
|
755
|
+
* Append a catalog-persistence task to the serialized `persistQueue`, so successive
|
|
756
|
+
* mutations (e.g. SET TAGS (a=1) then SET TAGS ()) apply in order and are drained by
|
|
757
|
+
* `closeAll`/`whenCatalogPersisted` before the provider closes. Errors are
|
|
758
|
+
* swallowed+logged to mirror `notifyChange`'s own try/catch contract — a listener
|
|
759
|
+
* rejection must never escape.
|
|
760
|
+
*/
|
|
761
|
+
private enqueuePersist;
|
|
762
|
+
/**
|
|
763
|
+
* Read-compare-write the catalog DDL for a table that just fired `table_modified`.
|
|
764
|
+
*
|
|
765
|
+
* - **Absent** catalog entry → the table is not store-backed in this catalog (a
|
|
766
|
+
* memory table in the same `db`, or a store table never persisted) → skip. This
|
|
767
|
+
* self-filters foreign-module tables without relying on `vtabModule` identity
|
|
768
|
+
* (which points at the isolation wrapper when wrapped).
|
|
769
|
+
* - **Present** but identical DDL → skip (no redundant write). This is what makes a
|
|
770
|
+
* structural ALTER — whose own `alterTable` already wrote the final DDL, then fires
|
|
771
|
+
* `table_modified` with that same final schema — a no-op here (no double-write).
|
|
772
|
+
* - **Present** and different DDL → re-persist (the tag swap; or a beneficial
|
|
773
|
+
* propagated rewrite of a dependent store table).
|
|
774
|
+
*/
|
|
775
|
+
private persistCatalogIfChanged;
|
|
776
|
+
/**
|
|
777
|
+
* Resolve once all catalog writes queued by async schema-change listeners
|
|
778
|
+
* (catalog-only tag swaps) have settled. A durability barrier: `closeAll` awaits
|
|
779
|
+
* it internally; callers/tests that need the persisted catalog current without a
|
|
780
|
+
* full close can await it directly. Never rejects — queued errors are logged in
|
|
781
|
+
* the chain.
|
|
782
|
+
*/
|
|
783
|
+
whenCatalogPersisted(): Promise<void>;
|
|
224
784
|
/**
|
|
225
785
|
* Close all stores.
|
|
226
786
|
*/
|