@quereus/store 4.5.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +34 -6
  2. package/dist/src/common/encoding.d.ts +4 -3
  3. package/dist/src/common/encoding.d.ts.map +1 -1
  4. package/dist/src/common/encoding.js +17 -13
  5. package/dist/src/common/encoding.js.map +1 -1
  6. package/dist/src/common/events.d.ts +31 -25
  7. package/dist/src/common/events.d.ts.map +1 -1
  8. package/dist/src/common/events.js +38 -39
  9. package/dist/src/common/events.js.map +1 -1
  10. package/dist/src/common/implicit-unique-index.d.ts +80 -0
  11. package/dist/src/common/implicit-unique-index.d.ts.map +1 -0
  12. package/dist/src/common/implicit-unique-index.js +148 -0
  13. package/dist/src/common/implicit-unique-index.js.map +1 -0
  14. package/dist/src/common/index.d.ts +7 -3
  15. package/dist/src/common/index.d.ts.map +1 -1
  16. package/dist/src/common/index.js +5 -2
  17. package/dist/src/common/index.js.map +1 -1
  18. package/dist/src/common/json-key.d.ts +30 -2
  19. package/dist/src/common/json-key.d.ts.map +1 -1
  20. package/dist/src/common/json-key.js +49 -2
  21. package/dist/src/common/json-key.js.map +1 -1
  22. package/dist/src/common/key-builder.d.ts +40 -20
  23. package/dist/src/common/key-builder.d.ts.map +1 -1
  24. package/dist/src/common/key-builder.js +31 -23
  25. package/dist/src/common/key-builder.js.map +1 -1
  26. package/dist/src/common/kv-store.d.ts +10 -0
  27. package/dist/src/common/kv-store.d.ts.map +1 -1
  28. package/dist/src/common/pk-key-resolution.d.ts +348 -0
  29. package/dist/src/common/pk-key-resolution.d.ts.map +1 -0
  30. package/dist/src/common/pk-key-resolution.js +481 -0
  31. package/dist/src/common/pk-key-resolution.js.map +1 -0
  32. package/dist/src/common/store-module-access-plan.d.ts +32 -0
  33. package/dist/src/common/store-module-access-plan.d.ts.map +1 -0
  34. package/dist/src/common/store-module-access-plan.js +428 -0
  35. package/dist/src/common/store-module-access-plan.js.map +1 -0
  36. package/dist/src/common/store-module-alter-column.d.ts +23 -0
  37. package/dist/src/common/store-module-alter-column.d.ts.map +1 -0
  38. package/dist/src/common/store-module-alter-column.js +389 -0
  39. package/dist/src/common/store-module-alter-column.js.map +1 -0
  40. package/dist/src/common/store-module-alter.d.ts +55 -0
  41. package/dist/src/common/store-module-alter.d.ts.map +1 -0
  42. package/dist/src/common/store-module-alter.js +504 -0
  43. package/dist/src/common/store-module-alter.js.map +1 -0
  44. package/dist/src/common/store-module-base.d.ts +268 -0
  45. package/dist/src/common/store-module-base.d.ts.map +1 -0
  46. package/dist/src/common/store-module-base.js +362 -0
  47. package/dist/src/common/store-module-base.js.map +1 -0
  48. package/dist/src/common/store-module-catalog.d.ts +269 -0
  49. package/dist/src/common/store-module-catalog.d.ts.map +1 -0
  50. package/dist/src/common/store-module-catalog.js +462 -0
  51. package/dist/src/common/store-module-catalog.js.map +1 -0
  52. package/dist/src/common/store-module-index-build.d.ts +88 -0
  53. package/dist/src/common/store-module-index-build.d.ts.map +1 -0
  54. package/dist/src/common/store-module-index-build.js +280 -0
  55. package/dist/src/common/store-module-index-build.js.map +1 -0
  56. package/dist/src/common/store-module-index.d.ts +116 -0
  57. package/dist/src/common/store-module-index.d.ts.map +1 -0
  58. package/dist/src/common/store-module-index.js +373 -0
  59. package/dist/src/common/store-module-index.js.map +1 -0
  60. package/dist/src/common/store-module-rename.d.ts +49 -0
  61. package/dist/src/common/store-module-rename.d.ts.map +1 -0
  62. package/dist/src/common/store-module-rename.js +268 -0
  63. package/dist/src/common/store-module-rename.js.map +1 -0
  64. package/dist/src/common/store-module-schema-rewrite.d.ts +57 -0
  65. package/dist/src/common/store-module-schema-rewrite.d.ts.map +1 -0
  66. package/dist/src/common/store-module-schema-rewrite.js +140 -0
  67. package/dist/src/common/store-module-schema-rewrite.js.map +1 -0
  68. package/dist/src/common/store-module-schema-sync.d.ts +232 -0
  69. package/dist/src/common/store-module-schema-sync.d.ts.map +1 -0
  70. package/dist/src/common/store-module-schema-sync.js +529 -0
  71. package/dist/src/common/store-module-schema-sync.js.map +1 -0
  72. package/dist/src/common/store-module.d.ts +43 -909
  73. package/dist/src/common/store-module.d.ts.map +1 -1
  74. package/dist/src/common/store-module.js +184 -3276
  75. package/dist/src/common/store-module.js.map +1 -1
  76. package/dist/src/common/store-table-base.d.ts +539 -0
  77. package/dist/src/common/store-table-base.d.ts.map +1 -0
  78. package/dist/src/common/store-table-base.js +900 -0
  79. package/dist/src/common/store-table-base.js.map +1 -0
  80. package/dist/src/common/store-table-constraints.d.ts +237 -0
  81. package/dist/src/common/store-table-constraints.d.ts.map +1 -0
  82. package/dist/src/common/store-table-constraints.js +624 -0
  83. package/dist/src/common/store-table-constraints.js.map +1 -0
  84. package/dist/src/common/store-table-scan.d.ts +452 -0
  85. package/dist/src/common/store-table-scan.d.ts.map +1 -0
  86. package/dist/src/common/store-table-scan.js +901 -0
  87. package/dist/src/common/store-table-scan.js.map +1 -0
  88. package/dist/src/common/store-table.d.ts +71 -1144
  89. package/dist/src/common/store-table.d.ts.map +1 -1
  90. package/dist/src/common/store-table.js +168 -2385
  91. package/dist/src/common/store-table.js.map +1 -1
  92. package/dist/src/testing/index.d.ts +12 -0
  93. package/dist/src/testing/index.d.ts.map +1 -0
  94. package/dist/src/testing/index.js +12 -0
  95. package/dist/src/testing/index.js.map +1 -0
  96. package/dist/src/testing/kv-assert.d.ts +19 -0
  97. package/dist/src/testing/kv-assert.d.ts.map +1 -0
  98. package/dist/src/testing/kv-assert.js +25 -0
  99. package/dist/src/testing/kv-assert.js.map +1 -0
  100. package/dist/src/testing/kv-conformance.d.ts.map +1 -1
  101. package/dist/src/testing/kv-conformance.js +1 -15
  102. package/dist/src/testing/kv-conformance.js.map +1 -1
  103. package/dist/src/testing/kv-provider-conformance.d.ts +54 -0
  104. package/dist/src/testing/kv-provider-conformance.d.ts.map +1 -0
  105. package/dist/src/testing/kv-provider-conformance.js +183 -0
  106. package/dist/src/testing/kv-provider-conformance.js.map +1 -0
  107. package/package.json +8 -8
@@ -0,0 +1,462 @@
1
+ /**
2
+ * The catalog store: how a table / view / materialized view's DDL text is turned
3
+ * into a persisted entry, written, compared, reloaded and removed, plus the two
4
+ * pieces of durable open/close bookkeeping that live beside it (the clean-shutdown
5
+ * marker and the stale-materialized-view set).
6
+ *
7
+ * Second layer of the store-module chain:
8
+ * StoreModuleBase -> StoreModuleCatalog -> StoreModuleSchemaSync -> StoreModuleIndex
9
+ * -> StoreModuleAlterColumn -> StoreModuleAlter -> StoreModuleRename -> StoreModule
10
+ */
11
+ import { exposedImplicitIndexes, generateIndexDDL, generateIndexTagsDDL, generateMaintainedTableDDL, generateTableDDL, generateViewDDL, isHiddenImplicitIndex, isMaintainedTable, } from '@quereus/quereus';
12
+ import { CLEAN_SHUTDOWN_META_NAME, STALE_MVS_META_NAME, buildCatalogKey, buildCatalogScanBounds, buildMaterializedViewCatalogKey, buildMetaCatalogKey, buildViewCatalogKey, classifyCatalogKey, } from './key-builder.js';
13
+ import { assertNoUnpairedSurrogate } from './encoding.js';
14
+ import { StoreModuleBase } from './store-module-base.js';
15
+ /**
16
+ * Guard the FULL persisted schema text, not just the object's own name: a lone
17
+ * surrogate anywhere in it — a quoted column name, a `default '…'` string literal, a
18
+ * `check` constraint's string constant — folds to U+FFFD under `TextEncoder` and would
19
+ * read back as different schema text than what was created. Independent of the
20
+ * catalog-key builders' identifier guard; it catches what they cannot see.
21
+ */
22
+ function assertPersistableDdlText(ddl) {
23
+ assertNoUnpairedSurrogate(ddl, 'persisted schema text');
24
+ }
25
+ /**
26
+ * The catalog entry a plain view would be persisted as. Building it is the check:
27
+ * {@link buildViewCatalogKey} rejects an unencodable name, and the caller then runs
28
+ * {@link assertPersistableDdlText} over `ddl`. Shared by the write path
29
+ * ({@link saveViewDDL}) and the pre-flight veto
30
+ * ({@link assertCatalogObjectPersistable}) so the two cannot drift.
31
+ */
32
+ function viewCatalogEntry(view) {
33
+ return {
34
+ key: buildViewCatalogKey(view.schemaName, view.name),
35
+ ddl: generateViewDDL(view),
36
+ };
37
+ }
38
+ /**
39
+ * The catalog entry a materialized view would be persisted as, or `undefined` when
40
+ * `table` carries no derivation (not an MV — nothing lands under the MV prefix).
41
+ * Counterpart of {@link viewCatalogEntry}; same write/veto sharing.
42
+ */
43
+ function maintainedViewCatalogEntry(table) {
44
+ if (!isMaintainedTable(table))
45
+ return undefined;
46
+ return {
47
+ key: buildMaterializedViewCatalogKey(table.schemaName, table.name),
48
+ ddl: generateMaintainedTableDDL(table),
49
+ };
50
+ }
51
+ export class StoreModuleCatalog extends StoreModuleBase {
52
+ /**
53
+ * Build the catalog entry for a table: its CREATE TABLE DDL, one
54
+ * `CREATE [UNIQUE] INDEX` statement per persistable secondary index, and one
55
+ * `alter index … set tags (…)` statement per exposed implicit index carrying
56
+ * user tags, newline-joined into a single multi-statement bundle keyed by
57
+ * `{schema}.{table}`.
58
+ *
59
+ * Bundling the indexes into the table's own entry means every existing
60
+ * re-persist path — `saveTableDDL` (each `alterTable` arm, `renameTable`) and
61
+ * the `table_modified` listener (`persistCatalogIfChanged`) — carries the
62
+ * indexes along for free, and `removeTableDDL` drops them with the table. The
63
+ * bundle is consumed on reopen by `rehydrateCatalog` → `importCatalog`, whose
64
+ * `parser.parseAll` splits it AST-by-AST (never on `\n`) and imports each
65
+ * statement in document order, so the table registers before its indexes.
66
+ *
67
+ * Both the table DDL and the index DDL are emitted without a `db` arg, keeping
68
+ * the persistence-safe fully-qualified form. Hidden implicit covering indexes
69
+ * (the auto-built BTree backing a declared UNIQUE constraint) are excluded —
70
+ * they are a backing detail that round-trips via the table's UNIQUE constraint,
71
+ * not as a standalone CREATE INDEX. For store tables `buildTableSchemaFromAST`
72
+ * synthesizes none of those, so in practice every index is included; the guard
73
+ * is defensive. A `CREATE UNIQUE INDEX`'s derived UNIQUE constraint is already
74
+ * excluded from the table DDL by the generator, so it round-trips solely via
75
+ * its own `CREATE UNIQUE INDEX` line — no doubling.
76
+ *
77
+ * An *exposed* implicit index (a non-derived UNIQUE constraint tagged
78
+ * `quereus.expose_implicit_index = true`, never materialized in store mode)
79
+ * must NOT get a synthetic `CREATE INDEX` line — re-import would materialize a
80
+ * real `IndexSchema` and change the store-mode shape. Its user tags
81
+ * (`UniqueConstraintSchema.exposedIndexTags`) instead ride a trailing
82
+ * `alter index … set tags` line, which `importDDL` re-applies silently after
83
+ * the CREATE TABLE in the same entry has registered the constraint. Empty tag
84
+ * records emit no line, and `exposedImplicitIndexes` returns `[]` for
85
+ * memory-mode tables (name materialized) and orders descriptors by the
86
+ * `uniqueConstraints` array, so the bundle stays byte-deterministic — which
87
+ * the compare-write in `persistCatalogIfChanged` relies on.
88
+ */
89
+ buildCatalogEntry(tableSchema) {
90
+ // `tableSchema` is always the engine-facing schema (no `_uc_*`; the store keeps the
91
+ // materialized enforcement copy internal), so hidden implicit indexes reach here
92
+ // only through the `uniqueConstraints`-driven `isHiddenImplicitIndex` /
93
+ // `exposedImplicitIndexes` paths — never as a materialized `CREATE INDEX`.
94
+ const parts = [generateTableDDL(tableSchema)];
95
+ for (const idx of tableSchema.indexes ?? []) {
96
+ if (isHiddenImplicitIndex(tableSchema, idx.name))
97
+ continue;
98
+ parts.push(generateIndexDDL(idx, tableSchema));
99
+ }
100
+ for (const desc of exposedImplicitIndexes(tableSchema)) {
101
+ if (desc.tags && Object.keys(desc.tags).length > 0) {
102
+ parts.push(generateIndexTagsDDL(tableSchema.schemaName, desc.name, desc.tags));
103
+ }
104
+ }
105
+ return parts.join('\n');
106
+ }
107
+ /**
108
+ * Encode a bundle of persisted schema text (DDL) for the catalog store.
109
+ * The guard is {@link assertPersistableDdlText} — see it for why the FULL text
110
+ * is checked, not just the object's name.
111
+ */
112
+ encodeCatalogDDL(ddl) {
113
+ assertPersistableDdlText(ddl);
114
+ return new TextEncoder().encode(ddl);
115
+ }
116
+ /**
117
+ * The catalog entry a plain table would be persisted as: its `{schema}.{table}` key
118
+ * (which {@link buildCatalogKey} refuses for an unencodable identifier) plus the DDL
119
+ * bundle {@link buildCatalogEntry} produces. Counterpart of {@link viewCatalogEntry} /
120
+ * {@link maintainedViewCatalogEntry}, and shared the same way — by both write paths
121
+ * ({@link saveTableDDL}, {@link persistTableCatalogEntryIfChanged}) and by the
122
+ * pre-flight veto ({@link assertCatalogObjectPersistable}) — so they cannot drift.
123
+ */
124
+ tableCatalogEntry(tableSchema) {
125
+ return {
126
+ key: buildCatalogKey(tableSchema.schemaName, tableSchema.name),
127
+ ddl: this.buildCatalogEntry(tableSchema),
128
+ };
129
+ }
130
+ /**
131
+ * Save table DDL (bundled with its secondary index DDL) to the catalog store.
132
+ * Unconditional put — every caller has just produced the authoritative bundle
133
+ * (create-index / drop-index / an `alterTable` arm / `renameTable`). For the
134
+ * event-driven `table_added` path, which may be re-delivered over an entry that
135
+ * is already current, use {@link persistTableCatalogEntryIfChanged}.
136
+ */
137
+ async saveTableDDL(tableSchema) {
138
+ const { key, ddl } = this.tableCatalogEntry(tableSchema);
139
+ const catalogStore = await this.provider.getCatalogStore();
140
+ await catalogStore.put(key, this.encodeCatalogDDL(ddl));
141
+ }
142
+ /**
143
+ * Compare-write a TABLE's catalog entry: write when the entry is absent or its
144
+ * bundle differs, skip when identical.
145
+ *
146
+ * Two callers, both needing exactly this shape:
147
+ * - the `table_added` arm of `StoreModuleSchemaSync.dispatchSchemaChange` — the
148
+ * authoritative write for a freshly registered store table, including one nobody
149
+ * ever reads or writes;
150
+ * - `StoreTableBase.initializeStore`'s first-storage-access backstop, which the
151
+ * `table_added` write normally renders a no-op.
152
+ *
153
+ * Distinct from both neighbours on purpose:
154
+ * - not {@link persistCatalogIfChanged}, whose absent → skip self-filter is exactly
155
+ * the case this path exists to cover (a brand-new table has no entry yet);
156
+ * - not bare {@link saveTableDDL}, which always puts — `table_added` is also
157
+ * delivered during rehydration for a store-hosted materialized-view backing, where
158
+ * the entry is already current and a blind put would dirty otherwise-identical
159
+ * catalog bytes.
160
+ *
161
+ * Shares {@link tableCatalogEntry} with the write and veto paths so all three agree
162
+ * on what a table's entry is.
163
+ */
164
+ async persistTableCatalogEntryIfChanged(tableSchema) {
165
+ const { key, ddl } = this.tableCatalogEntry(tableSchema);
166
+ await this.persistObjectCatalogEntryIfChanged(key, ddl);
167
+ }
168
+ /**
169
+ * Remove DDL from the catalog store when a table is dropped.
170
+ */
171
+ async removeTableDDL(schemaName, tableName) {
172
+ const catalogKey = buildCatalogKey(schemaName, tableName);
173
+ const catalogStore = await this.provider.getCatalogStore();
174
+ await catalogStore.delete(catalogKey);
175
+ }
176
+ /**
177
+ * Persist a plain view's catalog entry (DDL via `generateViewDDL`), keyed by its
178
+ * reserved view prefix. Compare-write (skip identical) — see
179
+ * {@link persistObjectCatalogEntryIfChanged}.
180
+ */
181
+ async saveViewDDL(view) {
182
+ const { key, ddl } = viewCatalogEntry(view);
183
+ await this.persistObjectCatalogEntryIfChanged(key, ddl);
184
+ }
185
+ /** Remove a plain view's catalog entry (on DROP VIEW). */
186
+ async removeViewDDL(schemaName, viewName) {
187
+ const catalogStore = await this.provider.getCatalogStore();
188
+ await catalogStore.delete(buildViewCatalogKey(schemaName, viewName));
189
+ }
190
+ /**
191
+ * Persist a materialized view's catalog entry (DDL via `generateMaintainedTableDDL`
192
+ * over the maintained table — the unified record), keyed by its reserved MV
193
+ * prefix. Compare-write (skip identical) — see
194
+ * {@link persistObjectCatalogEntryIfChanged}.
195
+ */
196
+ async saveMaterializedViewDDL(mv) {
197
+ const entry = maintainedViewCatalogEntry(mv);
198
+ // Callers only reach here with a maintained table; the guard keeps the shared
199
+ // helper's `undefined` arm honest rather than asserting non-null.
200
+ if (!entry)
201
+ return;
202
+ await this.persistObjectCatalogEntryIfChanged(entry.key, entry.ddl);
203
+ }
204
+ /** Remove a materialized view's catalog entry (on DROP MATERIALIZED VIEW). */
205
+ async removeMaterializedViewDDL(schemaName, mvName) {
206
+ const catalogStore = await this.provider.getCatalogStore();
207
+ await catalogStore.delete(buildMaterializedViewCatalogKey(schemaName, mvName));
208
+ }
209
+ /**
210
+ * Pre-flight veto (see `VirtualTableModule.assertCatalogObjectPersistable`): refuse a
211
+ * view / materialized view / table whose catalog entry this module could not durably
212
+ * write.
213
+ *
214
+ * It runs exactly the derivation the write path runs — {@link viewCatalogEntry} /
215
+ * {@link maintainedViewCatalogEntry} / {@link tableCatalogEntry} build the key
216
+ * (rejecting an unencodable identifier) and {@link assertPersistableDdlText} checks the
217
+ * generated DDL text — so the veto and the write cannot disagree about what is
218
+ * persistable. It is the only synchronous path a rejection can travel: the actual save
219
+ * is chained onto `persistQueue` behind a `SchemaChangeNotifier` listener, and both
220
+ * layers swallow.
221
+ *
222
+ * The `'table'` kind arrives only from the RENAME pre-flight scan, which offers EVERY
223
+ * table in every schema whose record the propagation would rewrite — most of them not
224
+ * ours. {@link ownsTableCatalogEntry} is the synchronous self-filter that keeps a
225
+ * memory-backed dependent from being refused on our behalf.
226
+ *
227
+ * Gated on `subscribedDb === db`: this module persists a view/MV only while subscribed
228
+ * to that `Database`'s change notifier. Since `StoreModule.onRegister` subscribes at
229
+ * `Database.registerModule` — before any statement can run — the gate no longer
230
+ * shields an unsubscribed module from vetoing; it now only declines to answer on
231
+ * behalf of a *different* `Database` than the one this module serves (the
232
+ * one-instance-one-database posture `ensureSchemaSubscription` documents). That case
233
+ * writes nothing, so vetoing there would reject a definition that loses nothing.
234
+ */
235
+ // NOTE: regenerates the object's full DDL text on every CREATE VIEW / CREATE MATERIALIZED
236
+ // VIEW / view-or-MV SET TAGS, and the persist that follows regenerates it again. DDL is
237
+ // rare and the text is small, so the duplicate render is not worth caching today; if a
238
+ // schema-heavy workload (a large `apply schema` deploying many views) ever shows up hot
239
+ // here, thread the already-built CatalogEntry from the veto through to the write.
240
+ assertCatalogObjectPersistable(db, kind, object) {
241
+ if (this.subscribedDb !== db)
242
+ return;
243
+ const entry = this.prospectiveCatalogEntry(kind, object);
244
+ if (entry)
245
+ assertPersistableDdlText(entry.ddl);
246
+ }
247
+ /**
248
+ * The entry {@link assertCatalogObjectPersistable} is being asked about, or `undefined`
249
+ * when this module would write none for it (a non-maintained table offered as
250
+ * `'materializedView'`, or a table we do not own).
251
+ */
252
+ prospectiveCatalogEntry(kind, object) {
253
+ switch (kind) {
254
+ case 'view': return viewCatalogEntry(object);
255
+ case 'materializedView': return maintainedViewCatalogEntry(object);
256
+ case 'table': {
257
+ const table = object;
258
+ return this.ownsTableCatalogEntry(table) ? this.tableCatalogEntry(table) : undefined;
259
+ }
260
+ }
261
+ }
262
+ /**
263
+ * Whether this module would be the one to write `table`'s catalog entry — the
264
+ * synchronous stand-in for the write path's absent-entry self-filter (see
265
+ * {@link persistCatalogIfChanged}), which reads the catalog store and so is unavailable
266
+ * to a hook that is synchronous and IO-free by contract.
267
+ *
268
+ * Also the self-filter for the `table_added` catalog write in
269
+ * `StoreModuleSchemaSync.dispatchSchemaChange`, which is likewise handed every table the
270
+ * engine registers regardless of who owns it.
271
+ *
272
+ * Ownership is tested exactly as `StoreModule.resolveOwnedTable` tests it: a table this
273
+ * session already touched (it is in `tables`), or one whose `vtabModule` is this module
274
+ * — or a WRAPPER exposing this module as `underlying`, which is what a store table looks
275
+ * like under `@quereus/quereus-isolation` (its `vtabModule` is the `IsolationModule`).
276
+ * Not ours ⇒ no entry ⇒ no check, which is what keeps a memory-backed dependent table in
277
+ * a database that also has store tables from being refused.
278
+ *
279
+ * It now agrees exactly with the `table_added` write path, which applies this same
280
+ * filter and then compare-writes ({@link persistTableCatalogEntryIfChanged}) with no
281
+ * absent → skip. It stays STRICTER than the `table_modified` write path
282
+ * ({@link persistCatalogIfChanged}), which skips an absent entry; refusing there is the
283
+ * safe side, since that table's own catalog write would throw on the diverged text
284
+ * anyway, on the swallowing persist queue with nothing left to tell the user.
285
+ */
286
+ ownsTableCatalogEntry(table) {
287
+ const tableKey = `${table.schemaName}.${table.name}`.toLowerCase();
288
+ if (this.tables.has(tableKey))
289
+ return true;
290
+ const owner = table.vtabModule;
291
+ return owner === this || owner?.underlying === this;
292
+ }
293
+ /**
294
+ * Compare-write a catalog entry: write only when the entry is absent or its DDL
295
+ * differs from `newDDL` (skip identical). Unlike {@link persistCatalogIfChanged}
296
+ * there is **no** absent→skip self-filter — the callers have already established
297
+ * that the object belongs in this db's catalog (a view/MV unconditionally, since
298
+ * one module instance serves one Database; a table via
299
+ * {@link persistTableCatalogEntryIfChanged}'s ownership filter). Skipping identical
300
+ * writes makes any event whose regenerated DDL is unchanged (e.g. a
301
+ * `materialized_view_refreshed`, or a `table_added` re-delivered for an
302
+ * already-persisted materialized-view backing during rehydration) a no-op, so a
303
+ * second consecutive reopen yields identical catalog bytes. (View/MV rehydration
304
+ * itself fires no view/MV events at all — `importCatalog` is silent.)
305
+ */
306
+ async persistObjectCatalogEntryIfChanged(key, newDDL) {
307
+ const catalogStore = await this.provider.getCatalogStore();
308
+ const existing = await catalogStore.get(key);
309
+ if (existing !== undefined && new TextDecoder().decode(existing) === newDDL)
310
+ return;
311
+ await catalogStore.put(key, this.encodeCatalogDDL(newDDL));
312
+ }
313
+ /**
314
+ * Read-compare-write the catalog DDL for a table that just fired `table_modified`.
315
+ *
316
+ * - **Absent** catalog entry → the table is not store-backed in this catalog (a
317
+ * memory table in the same `db`, or a store table never persisted) → skip. This
318
+ * self-filters foreign-module tables without relying on `vtabModule` identity
319
+ * (which points at the isolation wrapper when wrapped).
320
+ * - **Present** but identical DDL → skip (no redundant write). This is what makes a
321
+ * structural ALTER — whose own `alterTable` already wrote the final DDL, then fires
322
+ * `table_modified` with that same final schema — a no-op here (no double-write).
323
+ * - **Present** and different DDL → re-persist (the tag swap; or a beneficial
324
+ * propagated rewrite of a dependent store table).
325
+ */
326
+ async persistCatalogIfChanged(key, newObject) {
327
+ const catalogStore = await this.provider.getCatalogStore();
328
+ const existing = await catalogStore.get(key);
329
+ if (existing === undefined)
330
+ return; // not store-backed in this catalog — skip
331
+ // Regenerate the full bundle (table DDL + index DDL + exposed-implicit-index
332
+ // tag DDL): a SET TAGS on an index fires `table_modified` on the OWNING table
333
+ // with the updated index in `tableSchema.indexes` — or, for an exposed
334
+ // implicit index, with the updated `exposedIndexTags` on the originating
335
+ // UNIQUE constraint — so the changed DDL re-persists here with no
336
+ // index-specific plumbing. An identical bundle is what makes a structural
337
+ // ALTER (and the createIndex follow-up event) a no-op — no double-write.
338
+ const newDDL = this.buildCatalogEntry(newObject);
339
+ const existingDDL = new TextDecoder().decode(existing);
340
+ if (existingDDL === newDDL)
341
+ return; // identical — no redundant write
342
+ await catalogStore.put(key, this.encodeCatalogDDL(newDDL));
343
+ }
344
+ /**
345
+ * Load all DDL **values** from the catalog store (keys discarded).
346
+ * Used to restore persisted tables on startup and by tests asserting persisted DDL.
347
+ * Note: this returns table, view, AND materialized-view entries intermixed —
348
+ * `StoreModuleSchemaSync.rehydrateCatalog` uses {@link loadCatalogEntries} (keys retained) to
349
+ * classify them. Meta entries (e.g. the clean-shutdown marker) are not DDL
350
+ * and are filtered out.
351
+ */
352
+ async loadAllDDL() {
353
+ const catalogStore = await this.provider.getCatalogStore();
354
+ const bounds = buildCatalogScanBounds();
355
+ const decoder = new TextDecoder();
356
+ const ddlStatements = [];
357
+ for await (const entry of catalogStore.iterate(bounds)) {
358
+ if (classifyCatalogKey(entry.key) === 'meta')
359
+ continue;
360
+ const ddl = decoder.decode(entry.value);
361
+ ddlStatements.push(ddl);
362
+ }
363
+ return ddlStatements;
364
+ }
365
+ /**
366
+ * Load every catalog entry as `{ key, ddl }`. Unlike {@link loadAllDDL} (values
367
+ * only) this retains the key so `StoreModuleSchemaSync.rehydrateCatalog` can classify each entry
368
+ * (table / view / materialized view) by its reserved key prefix.
369
+ */
370
+ async loadCatalogEntries() {
371
+ const catalogStore = await this.provider.getCatalogStore();
372
+ const decoder = new TextDecoder();
373
+ const entries = [];
374
+ for await (const entry of catalogStore.iterate(buildCatalogScanBounds())) {
375
+ entries.push({ key: entry.key, ddl: decoder.decode(entry.value) });
376
+ }
377
+ return entries;
378
+ }
379
+ /**
380
+ * Consume the clean-shutdown marker, deleting it in the same breath (single-use).
381
+ *
382
+ * Returns `{ trusted, staleAtClose }`:
383
+ * - `trusted` — the marker was present AND its payload parsed as a string array.
384
+ * Absence (a fresh store, a crash, or a prior rehydrate without an intervening
385
+ * `StoreModule.closeAll`) yields `false`, so every adopt this session falls back to
386
+ * refill.
387
+ * - `staleAtClose` — the qualified lowercased `schema.mv` names that were
388
+ * stale-at-close (written by `StoreModule.closeAll`); those MVs refill even under trust.
389
+ *
390
+ * Conservative parse: any unparseable / wrong-shape payload (including a legacy
391
+ * bare `'1'`, which parses to a number, not an array) degrades to
392
+ * `{ trusted: false, staleAtClose: ∅ }` — refill everything — rather than
393
+ * trust-everything. The marker is deleted regardless of parse outcome.
394
+ */
395
+ async consumeCleanShutdownMarker() {
396
+ const catalogStore = await this.provider.getCatalogStore();
397
+ const markerKey = buildMetaCatalogKey(CLEAN_SHUTDOWN_META_NAME);
398
+ const raw = await catalogStore.get(markerKey);
399
+ if (raw === undefined)
400
+ return { trusted: false, staleAtClose: new Set() };
401
+ // Single-use AND durable-before-session-writes: forcing this delete to stable
402
+ // storage before any of the session's (independently flushed, different-store)
403
+ // data writes can land closes the power-loss window where a persisted data write
404
+ // outlives a lost marker-delete and resurrects a consumed marker. (Backends
405
+ // without a durability knob no-op the hint — losing it is conservative there, and
406
+ // memory has no crash.) See docs/mv-backing-host.md § Cross-module atomicity.
407
+ await catalogStore.delete(markerKey, { sync: true }); // single-use, regardless of parse outcome
408
+ try {
409
+ const parsed = JSON.parse(new TextDecoder().decode(raw));
410
+ if (!Array.isArray(parsed) || !parsed.every((s) => typeof s === 'string')) {
411
+ console.warn('[StoreModule] clean-shutdown marker payload is not a string array; refilling all backings.');
412
+ return { trusted: false, staleAtClose: new Set() };
413
+ }
414
+ return { trusted: true, staleAtClose: new Set(parsed) };
415
+ }
416
+ catch (e) {
417
+ console.warn(`[StoreModule] clean-shutdown marker payload did not parse as JSON; refilling all backings: ${e instanceof Error ? e.message : String(e)}`);
418
+ return { trusted: false, staleAtClose: new Set() };
419
+ }
420
+ }
421
+ /**
422
+ * Write the durable stale-MV set (`\x00meta\x00stale_mvs`) with `sync: true`. Unlike
423
+ * the clean-shutdown marker this entry is persistent current-truth — overwritten as
424
+ * staleness changes, never deleted — so a crash leaves the last synced value intact.
425
+ * The `sync` mirrors the marker-consume delete's durability discipline (and carries
426
+ * the same documented backend caveat: a backend without a durability knob no-ops it).
427
+ */
428
+ async writeDurableStaleMvSet(json) {
429
+ const catalogStore = await this.provider.getCatalogStore();
430
+ await catalogStore.put(buildMetaCatalogKey(STALE_MVS_META_NAME), new TextEncoder().encode(json), { sync: true });
431
+ }
432
+ /**
433
+ * Read + conservative-parse the durable stale-MV set, mirroring
434
+ * {@link consumeCleanShutdownMarker}'s parse discipline (but READ-only — never
435
+ * deleted). Returns:
436
+ * - `{ present: false }` — no entry on disk (a pre-atomic store, or an upgrade where
437
+ * the capability was gained but no stale-set has been written yet) → the caller
438
+ * falls back to the clean-shutdown marker path.
439
+ * - `{ present: true, stale }` — the parsed set of qualified `schema.mv` names.
440
+ * - `{ present: true, stale: null }` — a present but unparseable / wrong-shape payload
441
+ * → the caller refills everything (the always-safe posture).
442
+ */
443
+ async readDurableStaleMvSet() {
444
+ const catalogStore = await this.provider.getCatalogStore();
445
+ const raw = await catalogStore.get(buildMetaCatalogKey(STALE_MVS_META_NAME));
446
+ if (raw === undefined)
447
+ return { present: false };
448
+ try {
449
+ const parsed = JSON.parse(new TextDecoder().decode(raw));
450
+ if (!Array.isArray(parsed) || !parsed.every((s) => typeof s === 'string')) {
451
+ console.warn('[StoreModule] durable stale-MV set payload is not a string array; refilling all backings.');
452
+ return { present: true, stale: null };
453
+ }
454
+ return { present: true, stale: new Set(parsed) };
455
+ }
456
+ catch (e) {
457
+ console.warn(`[StoreModule] durable stale-MV set payload did not parse as JSON; refilling all backings: ${e instanceof Error ? e.message : String(e)}`);
458
+ return { present: true, stale: null };
459
+ }
460
+ }
461
+ }
462
+ //# sourceMappingURL=store-module-catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-module-catalog.js","sourceRoot":"","sources":["../../../src/common/store-module-catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Standalone index-population and uniqueness-validation helpers: streaming a table's
3
+ * committed rows into a secondary index's physical store in bounded batches, and the
4
+ * duplicate probes that judge a UNIQUE constraint or UNIQUE index against a row stream
5
+ * before it is enforced.
6
+ *
7
+ * Free functions rather than a layer of the store-module chain — none of them reads
8
+ * module state; each takes the stores and schema it needs.
9
+ */
10
+ import type { EffectiveRowSource, KeyNormalizerResolver, Row, TableIndexSchema, TableSchema, UniqueConstraintSchema } from '@quereus/quereus';
11
+ import type { KVEntry, KVStore } from './kv-store.js';
12
+ import type { StoreTable } from './store-table.js';
13
+ /**
14
+ * Build index entries for all existing rows in a table.
15
+ *
16
+ * `dataEntries` is the row stream to index. Callers choose its visibility:
17
+ * `createIndex` passes the table's EFFECTIVE stream (committed + pending, so
18
+ * an open transaction's rows are indexed too), while `rebuildSecondaryIndexes`
19
+ * passes the raw committed stream — it runs immediately after an ALTER has
20
+ * re-encoded the data store in place, and any pending ops still address the
21
+ * pre-ALTER key bytes.
22
+ *
23
+ * For UNIQUE indexes, performs an in-pass duplicate check (honoring partial
24
+ * predicates and SQL NULL semantics: multiple NULLs are allowed) and throws
25
+ * CONSTRAINT before any entries are written. Mirrors the memory module's
26
+ * populateNewIndex so `CREATE UNIQUE INDEX` over duplicated data fails
27
+ * atomically. `skipDuplicateCheck` suppresses it: `createIndex` sets it when a
28
+ * wrapper module supplied the rows to judge, having already validated them (see
29
+ * {@link validateUniqueIndexOverRows}). Judging `dataEntries` too would reject a
30
+ * committed duplicate the wrapper's transaction has already deleted.
31
+ *
32
+ * `normalizers` MUST be the owning connection's `db.getKeyNormalizerResolver()` —
33
+ * the same resolver `StoreTable.encodeOptions` carries. A rebuild that resolved
34
+ * collations any other way would re-encode the PK suffix under different bytes than
35
+ * the table writes at maintenance time, silently corrupting the index.
36
+ */
37
+ export declare function buildIndexEntries(dataEntries: AsyncIterable<KVEntry>, indexStore: KVStore, tableSchema: TableSchema, indexSchema: TableIndexSchema, keyCollation: string, normalizers: KeyNormalizerResolver, skipDuplicateCheck?: boolean, maxBatchBytes?: number): Promise<void>;
38
+ /**
39
+ * Validates `rows` against a UNIQUE constraint, throwing `CONSTRAINT` on the first
40
+ * duplicate before any schema mutation. Used by `ADD CONSTRAINT UNIQUE` (validate
41
+ * against the current collation) and by `SET COLLATE` (pass an `updatedSchema` whose
42
+ * altered column carries the NEW collation, so the dedup is performed under it).
43
+ * Mirrors the duplicate detection in {@link buildIndexEntries}: a `seen` Set keyed on
44
+ * a per-column collation-aware signature of the constrained values, with SQL NULL
45
+ * semantics (a row with any NULL constrained value never counts as a duplicate) and
46
+ * the partial `predicate` honored.
47
+ *
48
+ * `rows` MUST be the rows the DDL-issuing connection can SEE. Ordinarily that is this
49
+ * table's EFFECTIVE stream (committed rows merged with the open transaction's own
50
+ * pending puts/deletes — `StoreTable.iterateEffectiveEntries`, adapted by
51
+ * {@link rowsFromEntries}); when a wrapper module holds the pending rows outside this
52
+ * module it hands them down as an `EffectiveRowSource` instead. Either way, a
53
+ * committed-only stream would let a duplicate inserted earlier in the same transaction
54
+ * slip past validation and land in the table once the transaction commits.
55
+ *
56
+ * No index store is written — store UNIQUE enforcement is a full-scan over
57
+ * `uniqueConstraints` at write time. The signature is built by
58
+ * {@link dedupeRowSignature} with one normalizer per constrained column, resolved from
59
+ * `tableSchema.columns[idx].collation` through the connection's
60
+ * `db.getKeyNormalizerResolver()`, so a per-column collation registered with
61
+ * `db.registerCollation` is honored (matching write-time `compareSqlValues`
62
+ * enforcement). A comparator-only collation raises: rows that cannot be bucketed
63
+ * cannot be deduped. Columns whose declared type can never hold text take the
64
+ * identity normalizer and so never raise.
65
+ */
66
+ export declare function validateUniqueOverExistingRows(rows: AsyncIterable<Row>, tableSchema: TableSchema, uc: UniqueConstraintSchema, keyNormalizers: KeyNormalizerResolver): Promise<void>;
67
+ /**
68
+ * Index-shaped twin of {@link validateUniqueOverExistingRows}, used by
69
+ * `StoreModuleIndex.createIndex` when a wrapper module supplies the rows to judge. Dedupes on the
70
+ * index's own columns under the index column's COLLATE (falling back to the table
71
+ * column's), so it enforces exactly what `buildIndexEntries`' suppressed in-pass check
72
+ * would have — over the wrapper's rows instead of this module's committed ones.
73
+ */
74
+ export declare function validateUniqueIndexOverRows(rows: AsyncIterable<Row>, tableSchema: TableSchema, indexSchema: TableIndexSchema, keyNormalizers: KeyNormalizerResolver): Promise<void>;
75
+ /** Adapts a raw KV entry stream into the row stream the uniqueness validators consume. */
76
+ export declare function rowsFromEntries(entries: AsyncIterable<KVEntry>): AsyncIterable<Row>;
77
+ /**
78
+ * The rows a DDL-issuing connection can SEE, as every pre-mutation probe in the ALTER
79
+ * path needs them: a wrapper module's `EffectiveRowSource` when the isolation layer
80
+ * holds this transaction's staged rows outside the store, else this table's own
81
+ * effective stream (committed rows merged with the module's buffered puts/deletes).
82
+ *
83
+ * One fresh stream per call — an async generator is single-shot, and an
84
+ * `EffectiveRowSource` is re-callable for exactly that reason, so a probe that needs
85
+ * two walks calls this twice.
86
+ */
87
+ export declare function effectiveDdlRows(table: StoreTable, rows?: EffectiveRowSource): AsyncIterable<Row>;
88
+ //# sourceMappingURL=store-module-index-build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-module-index-build.d.ts","sourceRoot":"","sources":["../../../src/common/store-module-index-build.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAEX,kBAAkB,EAElB,qBAAqB,EACrB,GAAG,EAEH,gBAAgB,EAChB,WAAW,EACX,sBAAsB,EACtB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAUtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAOnD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,iBAAiB,CACtC,WAAW,EAAE,aAAa,CAAC,OAAO,CAAC,EACnC,UAAU,EAAE,OAAO,EACnB,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,gBAAgB,EAC7B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,qBAAqB,EAClC,kBAAkB,UAAQ,EAC1B,aAAa,GAAE,MAAgC,GAC7C,OAAO,CAAC,IAAI,CAAC,CA4Gf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,8BAA8B,CACnD,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,EACxB,WAAW,EAAE,WAAW,EACxB,EAAE,EAAE,sBAAsB,EAC1B,cAAc,EAAE,qBAAqB,GACnC,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAChD,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,EACxB,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,gBAAgB,EAC7B,cAAc,EAAE,qBAAqB,GACnC,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,0FAA0F;AAC1F,wBAAuB,eAAe,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,CAI1F;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,aAAa,CAAC,GAAG,CAAC,CAEjG"}