@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,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"}
|