@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.
- package/README.md +34 -6
- package/dist/src/common/encoding.d.ts +4 -3
- package/dist/src/common/encoding.d.ts.map +1 -1
- package/dist/src/common/encoding.js +17 -13
- package/dist/src/common/encoding.js.map +1 -1
- package/dist/src/common/events.d.ts +31 -25
- package/dist/src/common/events.d.ts.map +1 -1
- package/dist/src/common/events.js +38 -39
- package/dist/src/common/events.js.map +1 -1
- package/dist/src/common/implicit-unique-index.d.ts +80 -0
- package/dist/src/common/implicit-unique-index.d.ts.map +1 -0
- package/dist/src/common/implicit-unique-index.js +148 -0
- package/dist/src/common/implicit-unique-index.js.map +1 -0
- package/dist/src/common/index.d.ts +7 -3
- package/dist/src/common/index.d.ts.map +1 -1
- package/dist/src/common/index.js +5 -2
- package/dist/src/common/index.js.map +1 -1
- package/dist/src/common/json-key.d.ts +30 -2
- package/dist/src/common/json-key.d.ts.map +1 -1
- package/dist/src/common/json-key.js +49 -2
- package/dist/src/common/json-key.js.map +1 -1
- package/dist/src/common/key-builder.d.ts +40 -20
- package/dist/src/common/key-builder.d.ts.map +1 -1
- package/dist/src/common/key-builder.js +31 -23
- package/dist/src/common/key-builder.js.map +1 -1
- package/dist/src/common/kv-store.d.ts +10 -0
- package/dist/src/common/kv-store.d.ts.map +1 -1
- package/dist/src/common/pk-key-resolution.d.ts +348 -0
- package/dist/src/common/pk-key-resolution.d.ts.map +1 -0
- package/dist/src/common/pk-key-resolution.js +481 -0
- package/dist/src/common/pk-key-resolution.js.map +1 -0
- package/dist/src/common/store-module-access-plan.d.ts +32 -0
- package/dist/src/common/store-module-access-plan.d.ts.map +1 -0
- package/dist/src/common/store-module-access-plan.js +428 -0
- package/dist/src/common/store-module-access-plan.js.map +1 -0
- package/dist/src/common/store-module-alter-column.d.ts +23 -0
- package/dist/src/common/store-module-alter-column.d.ts.map +1 -0
- package/dist/src/common/store-module-alter-column.js +389 -0
- package/dist/src/common/store-module-alter-column.js.map +1 -0
- package/dist/src/common/store-module-alter.d.ts +55 -0
- package/dist/src/common/store-module-alter.d.ts.map +1 -0
- package/dist/src/common/store-module-alter.js +504 -0
- package/dist/src/common/store-module-alter.js.map +1 -0
- package/dist/src/common/store-module-base.d.ts +268 -0
- package/dist/src/common/store-module-base.d.ts.map +1 -0
- package/dist/src/common/store-module-base.js +362 -0
- package/dist/src/common/store-module-base.js.map +1 -0
- package/dist/src/common/store-module-catalog.d.ts +269 -0
- package/dist/src/common/store-module-catalog.d.ts.map +1 -0
- package/dist/src/common/store-module-catalog.js +462 -0
- package/dist/src/common/store-module-catalog.js.map +1 -0
- package/dist/src/common/store-module-index-build.d.ts +88 -0
- package/dist/src/common/store-module-index-build.d.ts.map +1 -0
- package/dist/src/common/store-module-index-build.js +280 -0
- package/dist/src/common/store-module-index-build.js.map +1 -0
- package/dist/src/common/store-module-index.d.ts +116 -0
- package/dist/src/common/store-module-index.d.ts.map +1 -0
- package/dist/src/common/store-module-index.js +373 -0
- package/dist/src/common/store-module-index.js.map +1 -0
- package/dist/src/common/store-module-rename.d.ts +49 -0
- package/dist/src/common/store-module-rename.d.ts.map +1 -0
- package/dist/src/common/store-module-rename.js +268 -0
- package/dist/src/common/store-module-rename.js.map +1 -0
- package/dist/src/common/store-module-schema-rewrite.d.ts +57 -0
- package/dist/src/common/store-module-schema-rewrite.d.ts.map +1 -0
- package/dist/src/common/store-module-schema-rewrite.js +140 -0
- package/dist/src/common/store-module-schema-rewrite.js.map +1 -0
- package/dist/src/common/store-module-schema-sync.d.ts +232 -0
- package/dist/src/common/store-module-schema-sync.d.ts.map +1 -0
- package/dist/src/common/store-module-schema-sync.js +529 -0
- package/dist/src/common/store-module-schema-sync.js.map +1 -0
- package/dist/src/common/store-module.d.ts +43 -909
- package/dist/src/common/store-module.d.ts.map +1 -1
- package/dist/src/common/store-module.js +184 -3276
- package/dist/src/common/store-module.js.map +1 -1
- package/dist/src/common/store-table-base.d.ts +539 -0
- package/dist/src/common/store-table-base.d.ts.map +1 -0
- package/dist/src/common/store-table-base.js +900 -0
- package/dist/src/common/store-table-base.js.map +1 -0
- package/dist/src/common/store-table-constraints.d.ts +237 -0
- package/dist/src/common/store-table-constraints.d.ts.map +1 -0
- package/dist/src/common/store-table-constraints.js +624 -0
- package/dist/src/common/store-table-constraints.js.map +1 -0
- package/dist/src/common/store-table-scan.d.ts +452 -0
- package/dist/src/common/store-table-scan.d.ts.map +1 -0
- package/dist/src/common/store-table-scan.js +901 -0
- package/dist/src/common/store-table-scan.js.map +1 -0
- package/dist/src/common/store-table.d.ts +71 -1144
- package/dist/src/common/store-table.d.ts.map +1 -1
- package/dist/src/common/store-table.js +168 -2385
- package/dist/src/common/store-table.js.map +1 -1
- package/dist/src/testing/index.d.ts +12 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +12 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/testing/kv-assert.d.ts +19 -0
- package/dist/src/testing/kv-assert.d.ts.map +1 -0
- package/dist/src/testing/kv-assert.js +25 -0
- package/dist/src/testing/kv-assert.js.map +1 -0
- package/dist/src/testing/kv-conformance.d.ts.map +1 -1
- package/dist/src/testing/kv-conformance.js +1 -15
- package/dist/src/testing/kv-conformance.js.map +1 -1
- package/dist/src/testing/kv-provider-conformance.d.ts +54 -0
- package/dist/src/testing/kv-provider-conformance.d.ts.map +1 -0
- package/dist/src/testing/kv-provider-conformance.js +183 -0
- package/dist/src/testing/kv-provider-conformance.js.map +1 -0
- package/package.json +8 -8
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module-wide state and storage plumbing shared by every layer of the generic
|
|
3
|
+
* KVStore-backed virtual-table module: the provider handle, the cached store and
|
|
4
|
+
* {@link StoreTable} maps, the single shared transaction coordinator, the
|
|
5
|
+
* serialized catalog-write queue, and the physical-store-name collision guard.
|
|
6
|
+
*
|
|
7
|
+
* Base of the store-module layer chain:
|
|
8
|
+
* StoreModuleBase -> StoreModuleCatalog -> StoreModuleSchemaSync -> StoreModuleIndex
|
|
9
|
+
* -> StoreModuleAlterColumn -> StoreModuleAlter -> StoreModuleRename -> StoreModule
|
|
10
|
+
*/
|
|
11
|
+
import type { BaseModuleConfig, Database, LensDeploymentSnapshot, SqlValue, TableSchema } from '@quereus/quereus';
|
|
12
|
+
import type { KVStore, KVStoreProvider } from './kv-store.js';
|
|
13
|
+
import type { StoreEventEmitter } from './events.js';
|
|
14
|
+
import { TransactionCoordinator } from './transaction.js';
|
|
15
|
+
import { StoreTable } from './store-table.js';
|
|
16
|
+
import type { StoreTableConfig } from './store-table-base.js';
|
|
17
|
+
/**
|
|
18
|
+
* Listener the host binds via `StoreModule.setLensDeploymentListener` to
|
|
19
|
+
* forward a logical `apply schema` lens deployment onward (typically to the sync
|
|
20
|
+
* layer's basis-table lifecycle bookkeeping). Kept as a plain callback so
|
|
21
|
+
* `@quereus/store` stays free of a `@quereus/sync` dependency; the worker — which
|
|
22
|
+
* depends on both — wires the two together.
|
|
23
|
+
*/
|
|
24
|
+
export type LensDeploymentListener = (db: Database, logicalSchemaName: string, snapshot: LensDeploymentSnapshot) => void | Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Configuration options for StoreModule tables.
|
|
27
|
+
*/
|
|
28
|
+
export interface StoreModuleConfig extends BaseModuleConfig {
|
|
29
|
+
/** Collation for text keys. Default: 'NOCASE'. */
|
|
30
|
+
collation?: 'BINARY' | 'NOCASE';
|
|
31
|
+
/**
|
|
32
|
+
* Serialized-byte budget for a single index-build write batch (see
|
|
33
|
+
* `buildIndexEntries` (store-module-index-build.ts)). Set from the `max_batch_bytes`
|
|
34
|
+
* module arg; a missing / non-positive value falls back to
|
|
35
|
+
* {@link DEFAULT_MAX_BATCH_BYTES}. Module-wide, not per-index.
|
|
36
|
+
*/
|
|
37
|
+
maxBatchBytes?: number;
|
|
38
|
+
/** Additional platform-specific options. */
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Default serialized-byte budget for a single index-build write batch. Bounds
|
|
43
|
+
* heap directly: `buildIndexEntries` (store-module-index-build.ts) flushes and starts a fresh
|
|
44
|
+
* batch whenever the accumulated key bytes cross this, so a table larger than
|
|
45
|
+
* memory never buffers its whole index in one batch. A byte budget (rather than a
|
|
46
|
+
* fixed entry count) is used because index entries vary in size, so bytes bound
|
|
47
|
+
* memory more tightly. 8 MiB trades a modest number of extra flushes against peak
|
|
48
|
+
* memory; override per store with `using store (max_batch_bytes = …)`.
|
|
49
|
+
*/
|
|
50
|
+
export declare const DEFAULT_MAX_BATCH_BYTES: number;
|
|
51
|
+
/**
|
|
52
|
+
* Build the set of physical store names this module's data/index stores
|
|
53
|
+
* currently occupy in `schemaName`, mapping each name to a human description
|
|
54
|
+
* of the logical object that owns it (for sited collision messages).
|
|
55
|
+
*
|
|
56
|
+
* Physical store names are built by string concatenation (`{schema}.{table}` /
|
|
57
|
+
* `{schema}.{table}_idx_{index}`) and the `_idx_` delimiter is itself a legal
|
|
58
|
+
* substring of any identifier, so two distinct logical objects (e.g. index
|
|
59
|
+
* `archive` on `t` and a sibling table `t_idx_archive`) can collapse to the
|
|
60
|
+
* same physical name. This set is the authoritative occupancy used by
|
|
61
|
+
* {@link StoreModuleBase.assertStoreNameFree} to reject such a collision at CREATE time.
|
|
62
|
+
*
|
|
63
|
+
* The occupancy is the union of two sources, robust to lazy connection and the
|
|
64
|
+
* isolation wrapper (see the ticket's "Enumeration source" section):
|
|
65
|
+
* 1. `tables` — every store table the owning module touched this session.
|
|
66
|
+
* 2. the target schema's catalog tables whose `vtabModule === owner` and which
|
|
67
|
+
* are not views — store-backed tables not yet lazily connected.
|
|
68
|
+
* Names embed the schema prefix, so cross-schema entries never collide and no
|
|
69
|
+
* per-schema filter on `tables` is needed. Memory-backed siblings and
|
|
70
|
+
* views own no store in this provider and are excluded (the `=== owner` /
|
|
71
|
+
* `!isView` filter) to avoid false-positive rejects.
|
|
72
|
+
*
|
|
73
|
+
* No self-exclusion: at each guarded call the candidate object is not yet
|
|
74
|
+
* registered (create/createIndex run before the engine adds it) so it cannot
|
|
75
|
+
* self-collide; and for renameTable the renamed table's OWN stores stay in the
|
|
76
|
+
* set deliberately. Any overlap between a name the rename introduces and an
|
|
77
|
+
* own current store is a footprint-swap rename (`t` with index `x` → `t_idx_x`,
|
|
78
|
+
* or table `u_idx_x` with index `x` → `u`) that providers cannot relocate
|
|
79
|
+
* safely — relocation order determines whether a source is clobbered before it
|
|
80
|
+
* is moved — while no benign rename produces such an overlap. Keeping own
|
|
81
|
+
* stores in the set therefore causes no false rejects and keeps the
|
|
82
|
+
* reject-before-any-side-effect guarantee uniform.
|
|
83
|
+
*
|
|
84
|
+
* `owner` is the module instance whose stores these are — the concrete `StoreModule` at
|
|
85
|
+
* the top of the layer chain, which this base layer cannot name without an import cycle.
|
|
86
|
+
* It is a parameter (rather than `this`) purely so the identity check keeps a real type
|
|
87
|
+
* instead of needing a cast; the enumeration itself reads no other module state.
|
|
88
|
+
*/
|
|
89
|
+
export declare function collectOccupiedStoreNames(tables: Iterable<StoreTable>, db: Database, schemaName: string, owner: object): Map<string, string>;
|
|
90
|
+
export declare abstract class StoreModuleBase {
|
|
91
|
+
protected provider: KVStoreProvider;
|
|
92
|
+
protected stores: Map<string, KVStore>;
|
|
93
|
+
/**
|
|
94
|
+
* The single transaction coordinator shared by every {@link StoreTable} this
|
|
95
|
+
* module owns — the unit of cross-table atomicity (see {@link getCoordinator}).
|
|
96
|
+
* Lives for the module's lifetime: a single table's drop must NOT evict it
|
|
97
|
+
* (sibling tables still use it); only `StoreModule.closeAll` clears it.
|
|
98
|
+
*/
|
|
99
|
+
protected moduleCoordinator?: TransactionCoordinator;
|
|
100
|
+
protected tables: Map<string, StoreTable>;
|
|
101
|
+
protected eventEmitter?: StoreEventEmitter;
|
|
102
|
+
/**
|
|
103
|
+
* Optional listener wired by the host (the worker) to forward a logical
|
|
104
|
+
* `apply schema` lens deployment to the sync layer's lifecycle bookkeeping.
|
|
105
|
+
* The store module is the only basis-backing host with both persistence and a
|
|
106
|
+
* `db` handle, so it is the forwarder; `@quereus/store` must not depend on
|
|
107
|
+
* `@quereus/sync`, so the listener is a plain callback the worker binds.
|
|
108
|
+
*/
|
|
109
|
+
protected lensDeploymentListener?: LensDeploymentListener;
|
|
110
|
+
/** Unsubscribe thunk for the engine `SchemaChangeNotifier` listener, set on first hook with a `db`. */
|
|
111
|
+
protected schemaListenerUnsub?: () => void;
|
|
112
|
+
/** The `Database` whose notifier we subscribed to. One module instance serves one `Database`. */
|
|
113
|
+
protected subscribedDb?: Database;
|
|
114
|
+
/**
|
|
115
|
+
* Serialized chain of pending catalog writes triggered by engine schema-change
|
|
116
|
+
* events (catalog-only tag swaps). `notifyChange` invokes listeners synchronously
|
|
117
|
+
* and does not await them, so the actual read-compare-write runs here, in order,
|
|
118
|
+
* and is drained by `closeAll` (and `whenCatalogPersisted`) before the provider closes.
|
|
119
|
+
*/
|
|
120
|
+
protected persistQueue: Promise<unknown>;
|
|
121
|
+
/**
|
|
122
|
+
* The JSON-serialized stale-MV set last enqueued onto {@link persistQueue} (the
|
|
123
|
+
* `\x00meta\x00stale_mvs` value), or `undefined` when no baseline is established
|
|
124
|
+
* for the current subscription. The schema-change listener compares each
|
|
125
|
+
* recomputed set against this so an unrelated `table_modified` (e.g. a tag swap
|
|
126
|
+
* that changes no MV's staleness) costs no extra fsync. Reset to `undefined`
|
|
127
|
+
* whenever a fresh `Database` is subscribed (`StoreModuleSchemaSync.ensureSchemaSubscription`) so
|
|
128
|
+
* a reopened module re-establishes the baseline from the first relevant event (or
|
|
129
|
+
* the rehydrate tail).
|
|
130
|
+
*/
|
|
131
|
+
protected lastPersistedStaleMvs: string | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* Whether the provider exposes an atomic cross-store commit domain
|
|
134
|
+
* ({@link KVStoreProvider.beginAtomicBatch}) — the capability the MV adopt fast
|
|
135
|
+
* path's gate-5 drop hinges on. Fixed for the provider's lifetime, so cached at
|
|
136
|
+
* construction (and used as the single gate for both writing AND reading the durable
|
|
137
|
+
* stale-MV set). Gating the **write** on it is load-bearing for soundness, not just an
|
|
138
|
+
* fsync saving: only an atomic-capable session — whose commits can never tear a source
|
|
139
|
+
* from its same-module backing — ever writes the durable set, so the set a later atomic
|
|
140
|
+
* session reads was necessarily written under that same no-tear guarantee. A persistent
|
|
141
|
+
* non-atomic provider therefore can never leave a torn-but-"not-stale" set for an atomic
|
|
142
|
+
* reopen to trust (it writes none); such a store falls back to the clean-shutdown marker.
|
|
143
|
+
*/
|
|
144
|
+
protected readonly atomicProvider: boolean;
|
|
145
|
+
constructor(provider: KVStoreProvider, eventEmitter?: StoreEventEmitter);
|
|
146
|
+
/**
|
|
147
|
+
* Get the KVStoreProvider used by this module.
|
|
148
|
+
*/
|
|
149
|
+
getProvider(): KVStoreProvider;
|
|
150
|
+
/**
|
|
151
|
+
* Get the event emitter for this module.
|
|
152
|
+
*/
|
|
153
|
+
getEventEmitter(): StoreEventEmitter | undefined;
|
|
154
|
+
/**
|
|
155
|
+
* Get a table by schema and name.
|
|
156
|
+
*/
|
|
157
|
+
getTable(schemaName: string, tableName: string): StoreTable | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* Parse module configuration from vtab args.
|
|
160
|
+
*/
|
|
161
|
+
protected parseConfig(args: Record<string, SqlValue> | undefined): StoreModuleConfig;
|
|
162
|
+
/**
|
|
163
|
+
* Get or create a data store for a table.
|
|
164
|
+
*/
|
|
165
|
+
getStore(schemaName: string, tableName: string, _config: StoreTableConfig): Promise<KVStore>;
|
|
166
|
+
/**
|
|
167
|
+
* Get or create an index store for a table.
|
|
168
|
+
*/
|
|
169
|
+
getIndexStore(schemaName: string, tableName: string, indexName: string): Promise<KVStore>;
|
|
170
|
+
/**
|
|
171
|
+
* Get or create a stats store for a table.
|
|
172
|
+
*/
|
|
173
|
+
getStatsStore(schemaName: string, tableName: string): Promise<KVStore>;
|
|
174
|
+
/**
|
|
175
|
+
* Get (lazily constructing) the SINGLE transaction coordinator shared by every
|
|
176
|
+
* table this module owns — the unit of cross-table atomicity. Every
|
|
177
|
+
* {@link StoreTable} attaches to the same instance, so a transaction touching
|
|
178
|
+
* several of the module's tables commits/rolls back as one all-or-nothing
|
|
179
|
+
* batch.
|
|
180
|
+
*
|
|
181
|
+
* Synchronously constructible: ops are addressed by explicit store handle, so
|
|
182
|
+
* the coordinator never needs to open a store to be created. This lets callers
|
|
183
|
+
* that must stay synchronous (e.g. a `BackingHost.connect()`) obtain a working
|
|
184
|
+
* coordinator before any table's store has been opened.
|
|
185
|
+
*/
|
|
186
|
+
getCoordinator(): TransactionCoordinator;
|
|
187
|
+
/**
|
|
188
|
+
* Flush the module's buffered writes before a DDL operation that physically
|
|
189
|
+
* rewrites or relocates storage.
|
|
190
|
+
*
|
|
191
|
+
* Such a DDL cannot honor the coordinator's buffer: pending ops are
|
|
192
|
+
* `(keyBytes, valueBytes, store)` triples computed at DML time under the
|
|
193
|
+
* PRE-DDL schema, and every physical rewrite (`StoreTable.rekeyRows` /
|
|
194
|
+
* `migrateRows`, the provider's directory move) reads and writes the COMMITTED
|
|
195
|
+
* store directly. Replaying stale-schema ops over the rewritten store on the
|
|
196
|
+
* eventual commit corrupts, loses, or misfiles those rows. So an `ALTER TABLE`
|
|
197
|
+
* that touches storage is effectively DDL-committing on a store-backed table:
|
|
198
|
+
* flush NOW, before the first physical read, so the rewrite sees every live row
|
|
199
|
+
* and there is nothing left to replay afterwards.
|
|
200
|
+
*
|
|
201
|
+
* Consequences, both deliberate:
|
|
202
|
+
* - The coordinator is module-wide, so this commits the WHOLE module
|
|
203
|
+
* transaction — every table's pending ops, not just the altered/renamed
|
|
204
|
+
* table's — in one all-or-nothing batch. An ALTER cannot half-commit some
|
|
205
|
+
* sibling tables.
|
|
206
|
+
* - Validation running AFTER this point (e.g. `ALTER PRIMARY KEY`'s duplicate-key
|
|
207
|
+
* pass — `rekeyRows` pass 1, which must see the flushed rows because a pending
|
|
208
|
+
* insert can itself be the duplicate) throws with the enclosing transaction
|
|
209
|
+
* already flushed. The store stays unmutated — only the transaction is gone.
|
|
210
|
+
* Validation that can read effectively belongs before the call, so the
|
|
211
|
+
* transaction survives it — the SET COLLATE PK re-key does exactly that
|
|
212
|
+
* (`StoreTable.validateRekeyedPrimaryKey` reads the effective rows, pending
|
|
213
|
+
* ops included, before this flush; `rekeyRows` pass 1 is only its backstop).
|
|
214
|
+
*
|
|
215
|
+
* Subsequent `commit()` calls on the same coordinator are no-ops
|
|
216
|
+
* (`inTransaction` is cleared), which keeps the enclosing transaction safe.
|
|
217
|
+
* The savepoint stack is cleared too, so a later `ROLLBACK TO` / `RELEASE` that
|
|
218
|
+
* the engine still broadcasts warns and degrades rather than throwing — see
|
|
219
|
+
* `TransactionCoordinator.rollbackToSavepoint`, which mirrors the memory
|
|
220
|
+
* module's identical posture.
|
|
221
|
+
*/
|
|
222
|
+
protected ddlCommitPendingOps(): Promise<void>;
|
|
223
|
+
/**
|
|
224
|
+
* Append a catalog-persistence task to the serialized `persistQueue`, so successive
|
|
225
|
+
* mutations (e.g. SET TAGS (a=1) then SET TAGS ()) apply in order and are drained by
|
|
226
|
+
* `closeAll`/`whenCatalogPersisted` before the provider closes. Errors are
|
|
227
|
+
* swallowed+logged to mirror `notifyChange`'s own try/catch contract — a listener
|
|
228
|
+
* rejection must never escape.
|
|
229
|
+
*/
|
|
230
|
+
protected enqueuePersist(work: () => Promise<void>): void;
|
|
231
|
+
/**
|
|
232
|
+
* Resolve once all catalog writes queued by async schema-change listeners
|
|
233
|
+
* (catalog-only tag swaps) have settled. A durability barrier: `closeAll` awaits
|
|
234
|
+
* it internally; callers/tests that need the persisted catalog current without a
|
|
235
|
+
* full close can await it directly. Never rejects — queued errors are logged in
|
|
236
|
+
* the chain.
|
|
237
|
+
*/
|
|
238
|
+
whenCatalogPersisted(): Promise<void>;
|
|
239
|
+
/**
|
|
240
|
+
* The physical index-store names a table owns for DROP / RENAME TABLE to relocate
|
|
241
|
+
* or reclaim: the MATERIALIZED index set — every explicit `CREATE INDEX` PLUS the
|
|
242
|
+
* hidden `_uc_*` realizing each plain UNIQUE. Reading only `getSchema().indexes`
|
|
243
|
+
* (the engine-facing schema, which carries no `_uc_*`) would strand a `_uc_*` store
|
|
244
|
+
* on disk after DROP TABLE, and — worse — after RENAME TABLE leave the renamed
|
|
245
|
+
* table seeking a freshly-created EMPTY `_uc_*` store, silently accepting a
|
|
246
|
+
* duplicate of a pre-rename row. Prefers the cached StoreTable's own materialized
|
|
247
|
+
* schema; falls back to materializing the schema-manager copy when the instance is
|
|
248
|
+
* already gone.
|
|
249
|
+
*/
|
|
250
|
+
protected materializedIndexNames(table: StoreTable | undefined, fallback: TableSchema | undefined): string[];
|
|
251
|
+
/**
|
|
252
|
+
* Throws `StatusCode.ERROR` when `candidate` (a physical store name produced by
|
|
253
|
+
* the key-builder for an object about to be created/renamed) already names an
|
|
254
|
+
* existing data or index store in `schemaName`. `candidateDesc` describes the
|
|
255
|
+
* incoming logical object; the message names the candidate physical store and
|
|
256
|
+
* both conflicting logical objects and is actionable (rename one of them).
|
|
257
|
+
*
|
|
258
|
+
* Must run BEFORE any storage side-effect (`getStore` / `getIndexStore` / the
|
|
259
|
+
* physical relocation): the provider opens/creates the store eagerly, so a
|
|
260
|
+
* guard that ran after would already have aliased the colliding object's store.
|
|
261
|
+
*
|
|
262
|
+
* Callers checking several candidates against the same occupancy (renameTable
|
|
263
|
+
* checks the new data store plus every relocated index store) pass a
|
|
264
|
+
* precomputed `occupied` map so the occupancy is collected once, not per call.
|
|
265
|
+
*/
|
|
266
|
+
protected assertStoreNameFree(db: Database, schemaName: string, candidate: string, candidateDesc: string, occupied?: Map<string, string>): void;
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=store-module-base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-module-base.d.ts","sourceRoot":"","sources":["../../../src/common/store-module-base.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module-wide state and storage plumbing shared by every layer of the generic
|
|
3
|
+
* KVStore-backed virtual-table module: the provider handle, the cached store and
|
|
4
|
+
* {@link StoreTable} maps, the single shared transaction coordinator, the
|
|
5
|
+
* serialized catalog-write queue, and the physical-store-name collision guard.
|
|
6
|
+
*
|
|
7
|
+
* Base of the store-module layer chain:
|
|
8
|
+
* StoreModuleBase -> StoreModuleCatalog -> StoreModuleSchemaSync -> StoreModuleIndex
|
|
9
|
+
* -> StoreModuleAlterColumn -> StoreModuleAlter -> StoreModuleRename -> StoreModule
|
|
10
|
+
*/
|
|
11
|
+
import { QuereusError, StatusCode } from '@quereus/quereus';
|
|
12
|
+
import { TransactionCoordinator } from './transaction.js';
|
|
13
|
+
import { withImplicitUniqueIndexes } from './implicit-unique-index.js';
|
|
14
|
+
import { buildDataStoreName, buildIndexStoreName } from './key-builder.js';
|
|
15
|
+
/**
|
|
16
|
+
* Default serialized-byte budget for a single index-build write batch. Bounds
|
|
17
|
+
* heap directly: `buildIndexEntries` (store-module-index-build.ts) flushes and starts a fresh
|
|
18
|
+
* batch whenever the accumulated key bytes cross this, so a table larger than
|
|
19
|
+
* memory never buffers its whole index in one batch. A byte budget (rather than a
|
|
20
|
+
* fixed entry count) is used because index entries vary in size, so bytes bound
|
|
21
|
+
* memory more tightly. 8 MiB trades a modest number of extra flushes against peak
|
|
22
|
+
* memory; override per store with `using store (max_batch_bytes = …)`.
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_MAX_BATCH_BYTES = 8 * 1024 * 1024; // 8 MiB
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the index-build batch byte budget from a `max_batch_bytes` module arg.
|
|
27
|
+
* A missing, non-numeric, or non-positive value falls back to
|
|
28
|
+
* {@link DEFAULT_MAX_BATCH_BYTES} — a zero or negative budget must NEVER disable
|
|
29
|
+
* flushing, which would restore the unbounded single-batch behavior this guards
|
|
30
|
+
* against.
|
|
31
|
+
*/
|
|
32
|
+
function resolveMaxBatchBytes(raw) {
|
|
33
|
+
let n;
|
|
34
|
+
if (typeof raw === 'number')
|
|
35
|
+
n = raw;
|
|
36
|
+
else if (typeof raw === 'bigint')
|
|
37
|
+
n = Number(raw);
|
|
38
|
+
else if (typeof raw === 'string' && raw.trim() !== '')
|
|
39
|
+
n = Number(raw);
|
|
40
|
+
else
|
|
41
|
+
return DEFAULT_MAX_BATCH_BYTES;
|
|
42
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
43
|
+
return DEFAULT_MAX_BATCH_BYTES;
|
|
44
|
+
return Math.floor(n);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build the set of physical store names this module's data/index stores
|
|
48
|
+
* currently occupy in `schemaName`, mapping each name to a human description
|
|
49
|
+
* of the logical object that owns it (for sited collision messages).
|
|
50
|
+
*
|
|
51
|
+
* Physical store names are built by string concatenation (`{schema}.{table}` /
|
|
52
|
+
* `{schema}.{table}_idx_{index}`) and the `_idx_` delimiter is itself a legal
|
|
53
|
+
* substring of any identifier, so two distinct logical objects (e.g. index
|
|
54
|
+
* `archive` on `t` and a sibling table `t_idx_archive`) can collapse to the
|
|
55
|
+
* same physical name. This set is the authoritative occupancy used by
|
|
56
|
+
* {@link StoreModuleBase.assertStoreNameFree} to reject such a collision at CREATE time.
|
|
57
|
+
*
|
|
58
|
+
* The occupancy is the union of two sources, robust to lazy connection and the
|
|
59
|
+
* isolation wrapper (see the ticket's "Enumeration source" section):
|
|
60
|
+
* 1. `tables` — every store table the owning module touched this session.
|
|
61
|
+
* 2. the target schema's catalog tables whose `vtabModule === owner` and which
|
|
62
|
+
* are not views — store-backed tables not yet lazily connected.
|
|
63
|
+
* Names embed the schema prefix, so cross-schema entries never collide and no
|
|
64
|
+
* per-schema filter on `tables` is needed. Memory-backed siblings and
|
|
65
|
+
* views own no store in this provider and are excluded (the `=== owner` /
|
|
66
|
+
* `!isView` filter) to avoid false-positive rejects.
|
|
67
|
+
*
|
|
68
|
+
* No self-exclusion: at each guarded call the candidate object is not yet
|
|
69
|
+
* registered (create/createIndex run before the engine adds it) so it cannot
|
|
70
|
+
* self-collide; and for renameTable the renamed table's OWN stores stay in the
|
|
71
|
+
* set deliberately. Any overlap between a name the rename introduces and an
|
|
72
|
+
* own current store is a footprint-swap rename (`t` with index `x` → `t_idx_x`,
|
|
73
|
+
* or table `u_idx_x` with index `x` → `u`) that providers cannot relocate
|
|
74
|
+
* safely — relocation order determines whether a source is clobbered before it
|
|
75
|
+
* is moved — while no benign rename produces such an overlap. Keeping own
|
|
76
|
+
* stores in the set therefore causes no false rejects and keeps the
|
|
77
|
+
* reject-before-any-side-effect guarantee uniform.
|
|
78
|
+
*
|
|
79
|
+
* `owner` is the module instance whose stores these are — the concrete `StoreModule` at
|
|
80
|
+
* the top of the layer chain, which this base layer cannot name without an import cycle.
|
|
81
|
+
* It is a parameter (rather than `this`) purely so the identity check keeps a real type
|
|
82
|
+
* instead of needing a cast; the enumeration itself reads no other module state.
|
|
83
|
+
*/
|
|
84
|
+
// NOTE: the builders below carry the unpaired-surrogate identifier guard, so this
|
|
85
|
+
// enumeration can THROW on an EXISTING schema object, not just on the incoming one —
|
|
86
|
+
// an unrelated CREATE/RENAME in the same schema would then fail with a confusing
|
|
87
|
+
// "cannot store the identifier ..." error. Unreachable today: `create` throws before
|
|
88
|
+
// the table is registered, and a catalog entry written before the guard existed was
|
|
89
|
+
// folded to a real U+FFFD character, which passes. If a lone-surrogate-named table ever
|
|
90
|
+
// does reach the schema manager by another route, skip such an object here (it occupies
|
|
91
|
+
// no store) rather than letting the enumeration throw.
|
|
92
|
+
export function collectOccupiedStoreNames(tables, db, schemaName, owner) {
|
|
93
|
+
const names = new Map();
|
|
94
|
+
const add = (s) => {
|
|
95
|
+
const dataName = buildDataStoreName(s.schemaName, s.name);
|
|
96
|
+
if (!names.has(dataName)) {
|
|
97
|
+
names.set(dataName, `data store of table '${s.schemaName}.${s.name}'`);
|
|
98
|
+
}
|
|
99
|
+
for (const idx of s.indexes ?? []) {
|
|
100
|
+
const idxName = buildIndexStoreName(s.schemaName, s.name, idx.name);
|
|
101
|
+
if (!names.has(idxName)) {
|
|
102
|
+
names.set(idxName, `index store of index '${idx.name}' on table '${s.schemaName}.${s.name}'`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
for (const t of tables) {
|
|
107
|
+
add(t.getSchema());
|
|
108
|
+
}
|
|
109
|
+
for (const t of db.schemaManager.getSchemaOrFail(schemaName).getAllTables()) {
|
|
110
|
+
if (t.vtabModule !== owner || t.isView)
|
|
111
|
+
continue;
|
|
112
|
+
add(t);
|
|
113
|
+
}
|
|
114
|
+
return names;
|
|
115
|
+
}
|
|
116
|
+
export class StoreModuleBase {
|
|
117
|
+
provider;
|
|
118
|
+
stores = new Map();
|
|
119
|
+
/**
|
|
120
|
+
* The single transaction coordinator shared by every {@link StoreTable} this
|
|
121
|
+
* module owns — the unit of cross-table atomicity (see {@link getCoordinator}).
|
|
122
|
+
* Lives for the module's lifetime: a single table's drop must NOT evict it
|
|
123
|
+
* (sibling tables still use it); only `StoreModule.closeAll` clears it.
|
|
124
|
+
*/
|
|
125
|
+
moduleCoordinator;
|
|
126
|
+
tables = new Map();
|
|
127
|
+
eventEmitter;
|
|
128
|
+
/**
|
|
129
|
+
* Optional listener wired by the host (the worker) to forward a logical
|
|
130
|
+
* `apply schema` lens deployment to the sync layer's lifecycle bookkeeping.
|
|
131
|
+
* The store module is the only basis-backing host with both persistence and a
|
|
132
|
+
* `db` handle, so it is the forwarder; `@quereus/store` must not depend on
|
|
133
|
+
* `@quereus/sync`, so the listener is a plain callback the worker binds.
|
|
134
|
+
*/
|
|
135
|
+
lensDeploymentListener;
|
|
136
|
+
/** Unsubscribe thunk for the engine `SchemaChangeNotifier` listener, set on first hook with a `db`. */
|
|
137
|
+
schemaListenerUnsub;
|
|
138
|
+
/** The `Database` whose notifier we subscribed to. One module instance serves one `Database`. */
|
|
139
|
+
subscribedDb;
|
|
140
|
+
/**
|
|
141
|
+
* Serialized chain of pending catalog writes triggered by engine schema-change
|
|
142
|
+
* events (catalog-only tag swaps). `notifyChange` invokes listeners synchronously
|
|
143
|
+
* and does not await them, so the actual read-compare-write runs here, in order,
|
|
144
|
+
* and is drained by `closeAll` (and `whenCatalogPersisted`) before the provider closes.
|
|
145
|
+
*/
|
|
146
|
+
persistQueue = Promise.resolve();
|
|
147
|
+
/**
|
|
148
|
+
* The JSON-serialized stale-MV set last enqueued onto {@link persistQueue} (the
|
|
149
|
+
* `\x00meta\x00stale_mvs` value), or `undefined` when no baseline is established
|
|
150
|
+
* for the current subscription. The schema-change listener compares each
|
|
151
|
+
* recomputed set against this so an unrelated `table_modified` (e.g. a tag swap
|
|
152
|
+
* that changes no MV's staleness) costs no extra fsync. Reset to `undefined`
|
|
153
|
+
* whenever a fresh `Database` is subscribed (`StoreModuleSchemaSync.ensureSchemaSubscription`) so
|
|
154
|
+
* a reopened module re-establishes the baseline from the first relevant event (or
|
|
155
|
+
* the rehydrate tail).
|
|
156
|
+
*/
|
|
157
|
+
lastPersistedStaleMvs;
|
|
158
|
+
/**
|
|
159
|
+
* Whether the provider exposes an atomic cross-store commit domain
|
|
160
|
+
* ({@link KVStoreProvider.beginAtomicBatch}) — the capability the MV adopt fast
|
|
161
|
+
* path's gate-5 drop hinges on. Fixed for the provider's lifetime, so cached at
|
|
162
|
+
* construction (and used as the single gate for both writing AND reading the durable
|
|
163
|
+
* stale-MV set). Gating the **write** on it is load-bearing for soundness, not just an
|
|
164
|
+
* fsync saving: only an atomic-capable session — whose commits can never tear a source
|
|
165
|
+
* from its same-module backing — ever writes the durable set, so the set a later atomic
|
|
166
|
+
* session reads was necessarily written under that same no-tear guarantee. A persistent
|
|
167
|
+
* non-atomic provider therefore can never leave a torn-but-"not-stale" set for an atomic
|
|
168
|
+
* reopen to trust (it writes none); such a store falls back to the clean-shutdown marker.
|
|
169
|
+
*/
|
|
170
|
+
atomicProvider;
|
|
171
|
+
constructor(provider, eventEmitter) {
|
|
172
|
+
this.provider = provider;
|
|
173
|
+
this.eventEmitter = eventEmitter;
|
|
174
|
+
this.atomicProvider = typeof provider.beginAtomicBatch === 'function';
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get the KVStoreProvider used by this module.
|
|
178
|
+
*/
|
|
179
|
+
getProvider() {
|
|
180
|
+
return this.provider;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get the event emitter for this module.
|
|
184
|
+
*/
|
|
185
|
+
getEventEmitter() {
|
|
186
|
+
return this.eventEmitter;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get a table by schema and name.
|
|
190
|
+
*/
|
|
191
|
+
getTable(schemaName, tableName) {
|
|
192
|
+
const tableKey = `${schemaName}.${tableName}`.toLowerCase();
|
|
193
|
+
return this.tables.get(tableKey);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Parse module configuration from vtab args.
|
|
197
|
+
*/
|
|
198
|
+
parseConfig(args) {
|
|
199
|
+
return {
|
|
200
|
+
collation: args?.collation || 'NOCASE',
|
|
201
|
+
// Index-build batch budget; malformed / non-positive clamps to the default
|
|
202
|
+
// so a bad arg can never disable the bounded-memory flush (see resolveMaxBatchBytes).
|
|
203
|
+
maxBatchBytes: resolveMaxBatchBytes(args?.max_batch_bytes),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// --- Engine schema-change subscription (catalog-only tag persistence) ---
|
|
207
|
+
/**
|
|
208
|
+
* Get or create a data store for a table.
|
|
209
|
+
*/
|
|
210
|
+
async getStore(schemaName, tableName, _config) {
|
|
211
|
+
const tableKey = `${schemaName}.${tableName}`.toLowerCase();
|
|
212
|
+
let store = this.stores.get(tableKey);
|
|
213
|
+
if (!store) {
|
|
214
|
+
store = await this.provider.getStore(schemaName, tableName);
|
|
215
|
+
if (!store) {
|
|
216
|
+
throw new Error(`Provider.getStore returned null/undefined for ${tableKey}`);
|
|
217
|
+
}
|
|
218
|
+
this.stores.set(tableKey, store);
|
|
219
|
+
}
|
|
220
|
+
return store;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get or create an index store for a table.
|
|
224
|
+
*/
|
|
225
|
+
async getIndexStore(schemaName, tableName, indexName) {
|
|
226
|
+
return this.provider.getIndexStore(schemaName, tableName, indexName);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get or create a stats store for a table.
|
|
230
|
+
*/
|
|
231
|
+
async getStatsStore(schemaName, tableName) {
|
|
232
|
+
return this.provider.getStatsStore(schemaName, tableName);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get (lazily constructing) the SINGLE transaction coordinator shared by every
|
|
236
|
+
* table this module owns — the unit of cross-table atomicity. Every
|
|
237
|
+
* {@link StoreTable} attaches to the same instance, so a transaction touching
|
|
238
|
+
* several of the module's tables commits/rolls back as one all-or-nothing
|
|
239
|
+
* batch.
|
|
240
|
+
*
|
|
241
|
+
* Synchronously constructible: ops are addressed by explicit store handle, so
|
|
242
|
+
* the coordinator never needs to open a store to be created. This lets callers
|
|
243
|
+
* that must stay synchronous (e.g. a `BackingHost.connect()`) obtain a working
|
|
244
|
+
* coordinator before any table's store has been opened.
|
|
245
|
+
*/
|
|
246
|
+
getCoordinator() {
|
|
247
|
+
if (!this.moduleCoordinator) {
|
|
248
|
+
this.moduleCoordinator = new TransactionCoordinator(this.eventEmitter,
|
|
249
|
+
// Re-evaluated per commit (see TransactionCoordinator.atomicBatchFactory)
|
|
250
|
+
// so a provider that gains/loses the capability is always honored.
|
|
251
|
+
() => this.provider.beginAtomicBatch?.());
|
|
252
|
+
}
|
|
253
|
+
return this.moduleCoordinator;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Flush the module's buffered writes before a DDL operation that physically
|
|
257
|
+
* rewrites or relocates storage.
|
|
258
|
+
*
|
|
259
|
+
* Such a DDL cannot honor the coordinator's buffer: pending ops are
|
|
260
|
+
* `(keyBytes, valueBytes, store)` triples computed at DML time under the
|
|
261
|
+
* PRE-DDL schema, and every physical rewrite (`StoreTable.rekeyRows` /
|
|
262
|
+
* `migrateRows`, the provider's directory move) reads and writes the COMMITTED
|
|
263
|
+
* store directly. Replaying stale-schema ops over the rewritten store on the
|
|
264
|
+
* eventual commit corrupts, loses, or misfiles those rows. So an `ALTER TABLE`
|
|
265
|
+
* that touches storage is effectively DDL-committing on a store-backed table:
|
|
266
|
+
* flush NOW, before the first physical read, so the rewrite sees every live row
|
|
267
|
+
* and there is nothing left to replay afterwards.
|
|
268
|
+
*
|
|
269
|
+
* Consequences, both deliberate:
|
|
270
|
+
* - The coordinator is module-wide, so this commits the WHOLE module
|
|
271
|
+
* transaction — every table's pending ops, not just the altered/renamed
|
|
272
|
+
* table's — in one all-or-nothing batch. An ALTER cannot half-commit some
|
|
273
|
+
* sibling tables.
|
|
274
|
+
* - Validation running AFTER this point (e.g. `ALTER PRIMARY KEY`'s duplicate-key
|
|
275
|
+
* pass — `rekeyRows` pass 1, which must see the flushed rows because a pending
|
|
276
|
+
* insert can itself be the duplicate) throws with the enclosing transaction
|
|
277
|
+
* already flushed. The store stays unmutated — only the transaction is gone.
|
|
278
|
+
* Validation that can read effectively belongs before the call, so the
|
|
279
|
+
* transaction survives it — the SET COLLATE PK re-key does exactly that
|
|
280
|
+
* (`StoreTable.validateRekeyedPrimaryKey` reads the effective rows, pending
|
|
281
|
+
* ops included, before this flush; `rekeyRows` pass 1 is only its backstop).
|
|
282
|
+
*
|
|
283
|
+
* Subsequent `commit()` calls on the same coordinator are no-ops
|
|
284
|
+
* (`inTransaction` is cleared), which keeps the enclosing transaction safe.
|
|
285
|
+
* The savepoint stack is cleared too, so a later `ROLLBACK TO` / `RELEASE` that
|
|
286
|
+
* the engine still broadcasts warns and degrades rather than throwing — see
|
|
287
|
+
* `TransactionCoordinator.rollbackToSavepoint`, which mirrors the memory
|
|
288
|
+
* module's identical posture.
|
|
289
|
+
*/
|
|
290
|
+
async ddlCommitPendingOps() {
|
|
291
|
+
if (this.moduleCoordinator?.isInTransaction()) {
|
|
292
|
+
await this.moduleCoordinator.commit();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Append a catalog-persistence task to the serialized `persistQueue`, so successive
|
|
297
|
+
* mutations (e.g. SET TAGS (a=1) then SET TAGS ()) apply in order and are drained by
|
|
298
|
+
* `closeAll`/`whenCatalogPersisted` before the provider closes. Errors are
|
|
299
|
+
* swallowed+logged to mirror `notifyChange`'s own try/catch contract — a listener
|
|
300
|
+
* rejection must never escape.
|
|
301
|
+
*/
|
|
302
|
+
enqueuePersist(work) {
|
|
303
|
+
this.persistQueue = this.persistQueue
|
|
304
|
+
.then(work)
|
|
305
|
+
.catch((err) => {
|
|
306
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
307
|
+
console.warn(`[StoreModule] Failed to persist catalog DDL after schema change: ${message}`);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Resolve once all catalog writes queued by async schema-change listeners
|
|
312
|
+
* (catalog-only tag swaps) have settled. A durability barrier: `closeAll` awaits
|
|
313
|
+
* it internally; callers/tests that need the persisted catalog current without a
|
|
314
|
+
* full close can await it directly. Never rejects — queued errors are logged in
|
|
315
|
+
* the chain.
|
|
316
|
+
*/
|
|
317
|
+
async whenCatalogPersisted() {
|
|
318
|
+
await this.persistQueue;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* The physical index-store names a table owns for DROP / RENAME TABLE to relocate
|
|
322
|
+
* or reclaim: the MATERIALIZED index set — every explicit `CREATE INDEX` PLUS the
|
|
323
|
+
* hidden `_uc_*` realizing each plain UNIQUE. Reading only `getSchema().indexes`
|
|
324
|
+
* (the engine-facing schema, which carries no `_uc_*`) would strand a `_uc_*` store
|
|
325
|
+
* on disk after DROP TABLE, and — worse — after RENAME TABLE leave the renamed
|
|
326
|
+
* table seeking a freshly-created EMPTY `_uc_*` store, silently accepting a
|
|
327
|
+
* duplicate of a pre-rename row. Prefers the cached StoreTable's own materialized
|
|
328
|
+
* schema; falls back to materializing the schema-manager copy when the instance is
|
|
329
|
+
* already gone.
|
|
330
|
+
*/
|
|
331
|
+
materializedIndexNames(table, fallback) {
|
|
332
|
+
const schema = table
|
|
333
|
+
? table.getMaterializedSchema()
|
|
334
|
+
: (fallback ? withImplicitUniqueIndexes(fallback) : undefined);
|
|
335
|
+
return (schema?.indexes ?? []).map(i => i.name);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Throws `StatusCode.ERROR` when `candidate` (a physical store name produced by
|
|
339
|
+
* the key-builder for an object about to be created/renamed) already names an
|
|
340
|
+
* existing data or index store in `schemaName`. `candidateDesc` describes the
|
|
341
|
+
* incoming logical object; the message names the candidate physical store and
|
|
342
|
+
* both conflicting logical objects and is actionable (rename one of them).
|
|
343
|
+
*
|
|
344
|
+
* Must run BEFORE any storage side-effect (`getStore` / `getIndexStore` / the
|
|
345
|
+
* physical relocation): the provider opens/creates the store eagerly, so a
|
|
346
|
+
* guard that ran after would already have aliased the colliding object's store.
|
|
347
|
+
*
|
|
348
|
+
* Callers checking several candidates against the same occupancy (renameTable
|
|
349
|
+
* checks the new data store plus every relocated index store) pass a
|
|
350
|
+
* precomputed `occupied` map so the occupancy is collected once, not per call.
|
|
351
|
+
*/
|
|
352
|
+
assertStoreNameFree(db, schemaName, candidate, candidateDesc, occupied) {
|
|
353
|
+
const occupiedBy = (occupied ?? collectOccupiedStoreNames(this.tables.values(), db, schemaName, this)).get(candidate);
|
|
354
|
+
if (occupiedBy !== undefined) {
|
|
355
|
+
throw new QuereusError(`Physical store-name collision: the ${candidateDesc} would map to physical store `
|
|
356
|
+
+ `'${candidate}', which already backs the ${occupiedBy}. These two objects would `
|
|
357
|
+
+ `share one physical store and silently corrupt each other. Rename the table or the `
|
|
358
|
+
+ `colliding index.`, StatusCode.ERROR);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
//# sourceMappingURL=store-module-base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-module-base.js","sourceRoot":"","sources":["../../../src/common/store-module-base.ts"],"names":[],"mappings":"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"}
|