@quereus/store 4.4.1 → 4.6.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 (104) hide show
  1. package/README.md +538 -491
  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 +5 -1
  7. package/dist/src/common/events.d.ts.map +1 -1
  8. package/dist/src/common/events.js.map +1 -1
  9. package/dist/src/common/implicit-unique-index.d.ts +80 -0
  10. package/dist/src/common/implicit-unique-index.d.ts.map +1 -0
  11. package/dist/src/common/implicit-unique-index.js +148 -0
  12. package/dist/src/common/implicit-unique-index.js.map +1 -0
  13. package/dist/src/common/index.d.ts +7 -3
  14. package/dist/src/common/index.d.ts.map +1 -1
  15. package/dist/src/common/index.js +5 -2
  16. package/dist/src/common/index.js.map +1 -1
  17. package/dist/src/common/json-key.d.ts +2 -2
  18. package/dist/src/common/json-key.js +2 -2
  19. package/dist/src/common/key-builder.d.ts +40 -20
  20. package/dist/src/common/key-builder.d.ts.map +1 -1
  21. package/dist/src/common/key-builder.js +31 -23
  22. package/dist/src/common/key-builder.js.map +1 -1
  23. package/dist/src/common/kv-store.d.ts +10 -0
  24. package/dist/src/common/kv-store.d.ts.map +1 -1
  25. package/dist/src/common/pk-key-resolution.d.ts +275 -0
  26. package/dist/src/common/pk-key-resolution.d.ts.map +1 -0
  27. package/dist/src/common/pk-key-resolution.js +389 -0
  28. package/dist/src/common/pk-key-resolution.js.map +1 -0
  29. package/dist/src/common/store-module-access-plan.d.ts +32 -0
  30. package/dist/src/common/store-module-access-plan.d.ts.map +1 -0
  31. package/dist/src/common/store-module-access-plan.js +424 -0
  32. package/dist/src/common/store-module-access-plan.js.map +1 -0
  33. package/dist/src/common/store-module-alter-column.d.ts +23 -0
  34. package/dist/src/common/store-module-alter-column.d.ts.map +1 -0
  35. package/dist/src/common/store-module-alter-column.js +393 -0
  36. package/dist/src/common/store-module-alter-column.js.map +1 -0
  37. package/dist/src/common/store-module-alter.d.ts +55 -0
  38. package/dist/src/common/store-module-alter.d.ts.map +1 -0
  39. package/dist/src/common/store-module-alter.js +519 -0
  40. package/dist/src/common/store-module-alter.js.map +1 -0
  41. package/dist/src/common/store-module-base.d.ts +268 -0
  42. package/dist/src/common/store-module-base.d.ts.map +1 -0
  43. package/dist/src/common/store-module-base.js +362 -0
  44. package/dist/src/common/store-module-base.js.map +1 -0
  45. package/dist/src/common/store-module-catalog.d.ts +269 -0
  46. package/dist/src/common/store-module-catalog.d.ts.map +1 -0
  47. package/dist/src/common/store-module-catalog.js +462 -0
  48. package/dist/src/common/store-module-catalog.js.map +1 -0
  49. package/dist/src/common/store-module-index-build.d.ts +88 -0
  50. package/dist/src/common/store-module-index-build.d.ts.map +1 -0
  51. package/dist/src/common/store-module-index-build.js +280 -0
  52. package/dist/src/common/store-module-index-build.js.map +1 -0
  53. package/dist/src/common/store-module-index.d.ts +116 -0
  54. package/dist/src/common/store-module-index.d.ts.map +1 -0
  55. package/dist/src/common/store-module-index.js +373 -0
  56. package/dist/src/common/store-module-index.js.map +1 -0
  57. package/dist/src/common/store-module-rename.d.ts +49 -0
  58. package/dist/src/common/store-module-rename.d.ts.map +1 -0
  59. package/dist/src/common/store-module-rename.js +244 -0
  60. package/dist/src/common/store-module-rename.js.map +1 -0
  61. package/dist/src/common/store-module-schema-rewrite.d.ts +57 -0
  62. package/dist/src/common/store-module-schema-rewrite.d.ts.map +1 -0
  63. package/dist/src/common/store-module-schema-rewrite.js +140 -0
  64. package/dist/src/common/store-module-schema-rewrite.js.map +1 -0
  65. package/dist/src/common/store-module-schema-sync.d.ts +232 -0
  66. package/dist/src/common/store-module-schema-sync.d.ts.map +1 -0
  67. package/dist/src/common/store-module-schema-sync.js +529 -0
  68. package/dist/src/common/store-module-schema-sync.js.map +1 -0
  69. package/dist/src/common/store-module.d.ts +43 -909
  70. package/dist/src/common/store-module.d.ts.map +1 -1
  71. package/dist/src/common/store-module.js +184 -3194
  72. package/dist/src/common/store-module.js.map +1 -1
  73. package/dist/src/common/store-table-base.d.ts +523 -0
  74. package/dist/src/common/store-table-base.d.ts.map +1 -0
  75. package/dist/src/common/store-table-base.js +878 -0
  76. package/dist/src/common/store-table-base.js.map +1 -0
  77. package/dist/src/common/store-table-constraints.d.ts +237 -0
  78. package/dist/src/common/store-table-constraints.d.ts.map +1 -0
  79. package/dist/src/common/store-table-constraints.js +624 -0
  80. package/dist/src/common/store-table-constraints.js.map +1 -0
  81. package/dist/src/common/store-table-scan.d.ts +415 -0
  82. package/dist/src/common/store-table-scan.d.ts.map +1 -0
  83. package/dist/src/common/store-table-scan.js +821 -0
  84. package/dist/src/common/store-table-scan.js.map +1 -0
  85. package/dist/src/common/store-table.d.ts +71 -1044
  86. package/dist/src/common/store-table.d.ts.map +1 -1
  87. package/dist/src/common/store-table.js +130 -2131
  88. package/dist/src/common/store-table.js.map +1 -1
  89. package/dist/src/testing/index.d.ts +12 -0
  90. package/dist/src/testing/index.d.ts.map +1 -0
  91. package/dist/src/testing/index.js +12 -0
  92. package/dist/src/testing/index.js.map +1 -0
  93. package/dist/src/testing/kv-assert.d.ts +19 -0
  94. package/dist/src/testing/kv-assert.d.ts.map +1 -0
  95. package/dist/src/testing/kv-assert.js +25 -0
  96. package/dist/src/testing/kv-assert.js.map +1 -0
  97. package/dist/src/testing/kv-conformance.d.ts.map +1 -1
  98. package/dist/src/testing/kv-conformance.js +1 -15
  99. package/dist/src/testing/kv-conformance.js.map +1 -1
  100. package/dist/src/testing/kv-provider-conformance.d.ts +54 -0
  101. package/dist/src/testing/kv-provider-conformance.d.ts.map +1 -0
  102. package/dist/src/testing/kv-provider-conformance.js +183 -0
  103. package/dist/src/testing/kv-provider-conformance.js.map +1 -0
  104. package/package.json +9 -9
@@ -8,224 +8,15 @@
8
8
  * - Data store: {schema}.{table} - row data keyed by encoded PK
9
9
  * - Index stores: {schema}.{table}_idx_{name} - one per secondary index
10
10
  * - Stats store: __stats__ - unified store for all table statistics, keyed by {schema}.{table}
11
- */
12
- import { VirtualTable, IndexConstraintOp, ConflictResolution, type LogicalType, type Database, type CollationFunction, type CollationResolver, type ColumnSchema, type TableSchema, type TableIndexSchema, type UniqueConstraintSchema, type Row, type FilterInfo, type SqlValue, type VirtualTableConnection, type UpdateArgs, type UpdateResult, type BackingRowChange } from '@quereus/quereus';
13
- import type { IterateOptions, KVEntry, KVStore } from './kv-store.js';
14
- import type { StoreEventEmitter } from './events.js';
15
- import type { TransactionCoordinator } from './transaction.js';
16
- import { StoreConnection } from './store-connection.js';
17
- import { type TableStats } from './serialization.js';
18
- import { type EncodeOptions, type KeyValueTransform } from './encoding.js';
19
- /**
20
- * Resolve the per-column KEY collation for each primary-key column.
21
- *
22
- * The store encodes PRIMARY KEY uniqueness/ordering PHYSICALLY in the key bytes, so each
23
- * text-capable PK column's key must be encoded under the collation the engine actually
24
- * COMPARES that column under. Returns one entry per PK member, in `pkDef` order:
25
- * - `isTextual` member (`text`) → its declared `collation` (normalized upper-case), or
26
- * `fallback` (the table key collation K) when the column carries none.
27
- * - text-capable but not `isTextual` member — `any`, `json`, and the temporal types
28
- * (`date` / `time` / `datetime` / `timespan`) → hard-coded `'BINARY'`, see below.
29
- * - never-text member → `undefined`: collation is meaningless for
30
- * integer/real/blob keys (they encode type-natively), so the encoder ignores
31
- * it and the data/index key bytes are identical regardless.
32
- *
33
- * Note that `undefined` does NOT mean "encode type-natively" to `encodeValue` — it means
34
- * "fall back to `options.collation`", the table key collation K. Only a never-text column
35
- * may be left `undefined`, because only it never reaches the collation branch.
36
- *
37
- * Every name returned here is encoded through the key-normalizer resolver carried
38
- * in `EncodeOptions.normalizers` (`db.getKeyNormalizerResolver()`), so a collation
39
- * registered or overridden with `db.registerCollation` produces key bytes that agree
40
- * with the comparator the store's UNIQUE enforcement uses. A collation that cannot
41
- * key — unregistered, or registered with a comparator but no normalizer — is rejected
42
- * at DDL time by {@link StoreTable.validateKeyCollations}, so no encode call ever has
43
- * to fall back. Shared by {@link StoreTable} (data-key + index-maintenance) and
44
- * `StoreModule.buildIndexEntries` (index rebuild) so the PK suffix encoding can
45
- * never drift between the two.
46
- */
47
- export declare function resolvePkKeyCollations(pkDef: ReadonlyArray<{
48
- index: number;
49
- }>, columns: ReadonlyArray<ColumnSchema>, fallback: string): (string | undefined)[];
50
- /**
51
- * The store's per-type key VALUE TRANSFORM for a semantic-ordering logical type, or
52
- * `undefined` when raw values already key faithfully.
53
- *
54
- * The contract is TWO-sided — identity AND order. `StoreTable` implements no
55
- * `comparePrimaryKey`, so the isolation layer merges overlay against underlying with
56
- * its own fallback comparator (the PK type's `compare` via `createTypedComparator`)
57
- * and needs this table's key BYTE order to agree with it; and byte equality must be
58
- * exactly the type's equality, or two spellings of one value land on two keys.
59
- *
60
- * - JSON → the store-local structural encoder ({@link jsonStructuralKey}): a
61
- * `Uint8Array` whose memcmp order reproduces the type's structural `compare`
62
- * (canonical-text bytes are identity-faithful but sort `[10]` before `[2]`,
63
- * which broke overlay shadowing — see json-key.ts).
64
- * - Everything else → the engine's {@link semanticKeyTransform} (the type's
65
- * `groupKey`). Today that is TIMESPAN: total seconds against the same reference
66
- * date as `TIMESPAN.compare`, so 'PT1H' and 'PT60M' collide on one key AND byte
67
- * order is elapsed-time order.
68
- *
69
- * JSON deliberately gets NO engine `groupKey` — `groupKey` also drives GROUP BY /
70
- * `IN` / hash-join identity, where canonical-text identity is already correct and a
71
- * `Uint8Array` key would be a behaviour change for no benefit. Hence this
72
- * store-local seam. A semantic-ordering type with NEITHER a `groupKey` nor an entry
73
- * here cannot key a persisted structure soundly; `StoreTable` rejects it at DDL time
74
- * (see `validateSemanticKeyTransforms`).
75
- */
76
- export declare function storeSemanticKeyTransform(type: LogicalType | undefined): KeyValueTransform | undefined;
77
- /**
78
- * Resolve the per-column key value transform for each primary-key column —
79
- * {@link storeSemanticKeyTransform} of the member's logical type. One entry per PK
80
- * member in `pkDef` order; `undefined` for every type whose raw value already keys
81
- * faithfully. The companion of {@link resolvePkKeyCollations}: every call site that
82
- * threads the collations MUST thread these too, or key identity/order drifts
83
- * between the write path and the rebuild/lookup path.
84
- */
85
- export declare function resolvePkKeyTransforms(pkDef: ReadonlyArray<{
86
- index: number;
87
- }>, columns: ReadonlyArray<ColumnSchema>): (KeyValueTransform | undefined)[];
88
- /**
89
- * Per-column key value transforms for a secondary index's OWN columns (the
90
- * index half of `buildIndexKey`), positionally aligned with `index.columns`.
91
- * Same rule as {@link resolvePkKeyTransforms}: a semantic-ordering member
92
- * canonicalizes through {@link storeSemanticKeyTransform}, everything else
93
- * encodes raw.
94
- */
95
- export declare function resolveIndexKeyTransforms(index: TableIndexSchema, columns: ReadonlyArray<ColumnSchema>): (KeyValueTransform | undefined)[];
96
- /**
97
- * True when `col` can produce a TEXT value at runtime, and so when its physical
98
- * key bytes are produced by a collation normalizer rather than a type-native
99
- * encoding. The `ColumnSchema`-shaped wrapper over the engine's
100
- * {@link logicalTypeCanHoldText}; both collation-safety guards over the store's
101
- * secondary indexes — the write-side {@link StoreTable.indexSeekHonorsEnforcementCollation}
102
- * and the read-side `StoreModule.tryIndexAccessPlan` — exempt a never-text column
103
- * from their K-vs-C comparison, so a false "non-text" answer is a silent
104
- * wrong-result (a seek under the wrong collation, with the residual dropped).
105
- */
106
- export declare function columnCanHoldText(col: ColumnSchema | undefined): boolean;
107
- /**
108
- * True when a byte window — or a byte-order advertisement — over `column` is SOUND.
109
- *
110
- * The store physically orders rows by memcmp of each column's key bytes, which for a
111
- * text-capable column are produced by `keyCollation`'s normalizer. A range seek (and the
112
- * `providesOrdering` / `monotonicOn` advertisement) equates that byte order with the order
113
- * `compareCollation`'s comparator defines. Two things must hold for that equation:
114
- *
115
- * - **Same collation on both sides** (`compareCollation === keyCollation`). A `keyCollation`
116
- * merely COARSER than `compareCollation` — the relaxation the EQUALITY seek is allowed to
117
- * make, see `StoreModule.tryIndexAccessPlan` — is not enough here: it would need the key
118
- * normalizer to be monotone with respect to the OTHER collation's order. It generally is
119
- * not, even for built-ins: with `keyCollation = NOCASE` and `compareCollation = BINARY`,
120
- * 'K' (U+212A KELVIN SIGN) is `> 'z'` under BINARY, yet its key bytes are
121
- * `toLowerCase('K') = 'k'`, which sorts BEFORE 'z' — the row falls outside a `> 'z'`
122
- * window and is silently dropped.
123
- * - **The normalizer preserves order** (`db._isCollationOrderPreserving`). `registerCollation`
124
- * promises only that a normalizer partitions strings the way the comparator calls them
125
- * equal; a custom pair may agree on equality and disagree on order.
126
- *
127
- * A never-text column is exempt: its key bytes are type-native and collation-independent.
128
- *
129
- * Returning `false` costs an optimization (the caller full-scans and lets the
130
- * collation-aware `matchesFilters` residual decide); returning `true` wrongly is a silent
131
- * wrong-result. Shared by `StoreTable`'s read arms and `StoreModule`'s access planner so the
132
- * "mark handled" and "build a window" decisions can never disagree.
133
- */
134
- export declare function keyOrderMatchesCollation(db: Database, column: ColumnSchema | undefined, keyCollation: string, compareCollation: string): boolean;
135
- /**
136
- * How many LEADING primary-key members have key bytes whose memcmp order matches their
137
- * declared collation's comparator order, per {@link keyOrderMatchesCollation}.
138
- *
139
- * `pkKeyCollations` is {@link resolvePkKeyCollations}' output for this table; an `undefined`
140
- * entry belongs to a never-text member, which {@link keyOrderMatchesCollation} exempts
141
- * outright — `tableKeyCollation` is passed only so the exempt branch has a name to ignore.
142
- * The comparison collation is the column's declared one, which `reconcilePkCollations` has
143
- * already aligned with the key collation for every *textual* PK member.
144
- *
145
- * What the advertisement is measured against is the order the planner's `Sort` would have
146
- * produced. For most members Sort orders under the operand's COLLATION, so a collation match
147
- * is the question; for a member whose logical type carries SEMANTIC ordering (TIMESPAN, JSON
148
- * — see docs/types.md "Semantic ordering") Sort now ranks by `logicalType.compare`, which no
149
- * text-byte encoding reproduces, and {@link keyOrderMatchesCollation} closes the gate for
150
- * such members outright (the memory backend's typed BTree order is the canonical order under
151
- * that ruling; the store declines rather than advertising a divergent byte order).
152
- *
153
- * `0` ⇒ even the leading member is unsafe: no range seek, no PK-order advertisement.
154
- * A prefix shorter than the PK truncates the ordering advertisement rather than voiding it.
155
- */
156
- export declare function pkOrderPreservingPrefixLength(db: Database, schema: TableSchema, pkKeyCollations: ReadonlyArray<string | undefined>, tableKeyCollation: string): number;
157
- /**
158
- * Deterministic name of the implicit covering index materialized for a
159
- * NON-DERIVED UNIQUE constraint `uc`: the constraint's own name when it has one,
160
- * else the auto-name `_uc_<colNames>`. This MUST equal the engine's
161
- * `catalog.ts` `implicitIndexName`, so a name that ever reaches the catalog
162
- * bundle generator is recognized as hidden / exposed-implicit rather than a user
163
- * index.
164
- */
165
- export declare function implicitUniqueIndexName(schema: TableSchema, uc: UniqueConstraintSchema): string;
166
- /**
167
- * Return `schema` with a synthetic secondary index materialized into
168
- * `schema.indexes` for every NON-DERIVED UNIQUE constraint that lacks one — the
169
- * store analogue of `MemoryTableManager.ensureUniqueConstraintIndexes`.
170
- *
171
- * This is what lets a plain column- or table-level `UNIQUE` be enforced by a
172
- * bounded index point-seek ({@link StoreTable.findUniqueConflictViaIndex})
173
- * instead of an O(rows) full scan: DML maintenance
174
- * ({@link StoreTable.updateSecondaryIndexes}) and the UNIQUE check both iterate
175
- * `schema.indexes`, so once the `_uc_*` entry is present the physical index is
176
- * maintained and seeked with no new code path.
177
- *
178
- * The result is held ONLY in {@link StoreTable.materializedSchema} (the
179
- * enforcement copy), never in the engine-facing `tableSchema` the store returns
180
- * from `getSchema()` / registers with the engine. So the read-query planner and
181
- * the catalog bundle never see `_uc_*` (it is fully derived on open from
182
- * `uniqueConstraints`); like the memory backend, a plain UNIQUE gets no read-side
183
- * plan from `_uc_*`, only enforcement / maintenance.
184
11
  *
185
- * Idempotent — a name already present is left untouched — and returns a frozen
186
- * schema (or the input unchanged when there is nothing to add). A
187
- * `derivedFromIndex` UC is skipped (its `CREATE UNIQUE INDEX` is already in
188
- * `schema.indexes`). A partial UNIQUE's synthetic index carries the SAME
189
- * `uc.predicate` object reference, which
190
- * {@link StoreTable.findIndexForUniqueConstraint} matches on identity.
12
+ * The class is layered across four files, each adding one job to the one below:
13
+ * `store-table-base.ts` - state, store handles, stats, transaction lifecycle
14
+ * `store-table-scan.ts` - the read path (predicate -> byte window -> rows)
15
+ * `store-table-constraints.ts` - secondary-index maintenance + UNIQUE enforcement
16
+ * `store-table.ts` - this file: the write path and bulk row maintenance
191
17
  */
192
- export declare function withImplicitUniqueIndexes(schema: TableSchema): TableSchema;
193
- /**
194
- * Configuration for a store table.
195
- */
196
- export interface StoreTableConfig {
197
- /** Collation for text keys. Default: 'NOCASE'. */
198
- collation?: 'BINARY' | 'NOCASE';
199
- /**
200
- * Serialized-byte budget for a single index-build write batch (see
201
- * `StoreModule.buildIndexEntries`). Parsed from the `max_batch_bytes` module
202
- * arg; undefined falls back to the module default.
203
- */
204
- maxBatchBytes?: number;
205
- /** Additional platform-specific options. */
206
- [key: string]: unknown;
207
- }
208
- /**
209
- * Interface for the store module that manages this table.
210
- * Provides access to stores and coordinators.
211
- */
212
- export interface StoreTableModule {
213
- /** Get the data store for a table. */
214
- getStore(schemaName: string, tableName: string, config: StoreTableConfig): Promise<KVStore>;
215
- /** Get an index store for a table. */
216
- getIndexStore(schemaName: string, tableName: string, indexName: string): Promise<KVStore>;
217
- /** Get the stats store for a table. */
218
- getStatsStore(schemaName: string, tableName: string): Promise<KVStore>;
219
- /**
220
- * Get the module's single shared transaction coordinator (one per storage
221
- * module, shared by every table — the unit of cross-table atomicity).
222
- * Synchronous: ops are addressed by explicit store handle, so obtaining a
223
- * coordinator never requires opening storage.
224
- */
225
- getCoordinator(): TransactionCoordinator;
226
- /** Save table DDL to persistent storage. */
227
- saveTableDDL(tableSchema: TableSchema): Promise<void>;
228
- }
18
+ import { type ColumnSchema, type Row, type SqlValue, type UpdateArgs, type UpdateResult, type BackingRowChange } from '@quereus/quereus';
19
+ import { StoreTableConstraints } from './store-table-constraints.js';
229
20
  /**
230
21
  * One externally-applied row op against a SOURCE table's committed storage,
231
22
  * the input vocabulary of {@link StoreTable.applyExternalRowChanges}. An
@@ -247,192 +38,7 @@ export type ExternalRowOp = {
247
38
  * storage backends. Platform-specific behavior is delegated to the
248
39
  * StoreTableModule.
249
40
  */
250
- export declare class StoreTable extends VirtualTable {
251
- protected storeModule: StoreTableModule;
252
- protected config: StoreTableConfig;
253
- protected store: KVStore | null;
254
- protected storeInitPromise: Promise<KVStore> | null;
255
- protected indexStores: Map<string, KVStore>;
256
- protected statsStore: KVStore | null;
257
- protected coordinator: TransactionCoordinator | null;
258
- /**
259
- * Disposer returned by the coordinator's `registerCallbacks`, captured on the
260
- * first {@link attachCoordinator}. Run by {@link dispose} at hard eviction to
261
- * deregister this instance's {stats apply/discard} pair from the module-wide
262
- * coordinator; null until attached, and nulled again after dispose.
263
- */
264
- private coordinatorDisposer;
265
- protected connection: StoreConnection | null;
266
- protected eventEmitter?: StoreEventEmitter;
267
- protected encodeOptions: EncodeOptions;
268
- protected pkDirections: boolean[];
269
- /**
270
- * Per-PK-column KEY collation (see {@link resolvePkKeyCollations}). Drives the
271
- * physical encoding of every data key and the PK suffix of every secondary-index
272
- * key, so a text PK column declared BINARY/NOCASE/RTRIM is keyed under its own
273
- * collation rather than one fixed table-level collation. Recomputed on every
274
- * {@link updateSchema} (an ALTER COLUMN SET COLLATE on a PK member changes it).
275
- */
276
- protected pkKeyCollations: (string | undefined)[];
277
- /**
278
- * Per-PK-column key-identity value transform (see {@link resolvePkKeyTransforms}).
279
- * Threaded alongside {@link pkKeyCollations} into every data-key / PK-suffix /
280
- * PK-prefix-bounds encode, so a semantic-ordering PK member (TIMESPAN) keys
281
- * semantically-equal spellings to one physical key. Recomputed on every
282
- * {@link updateSchema}.
283
- */
284
- protected pkKeyTransforms: (KeyValueTransform | undefined)[];
285
- /**
286
- * Per-PK-column typed EQUALITY comparator for the semantic-ordering members only —
287
- * `undefined` for every other member, which {@link keysEqual} still compares with
288
- * strict `!==` as before. Keeps self-PK exclusion and PK-identity questions in
289
- * agreement with the physical key identity above ('PT1H' row IS the 'PT60M' row).
290
- */
291
- protected pkSemanticEquality: (((a: SqlValue, b: SqlValue) => number) | undefined)[];
292
- /**
293
- * `db.getCollationResolver()`, bound once at construction. Every collation-aware
294
- * VALUE comparison this table makes — pushed-constraint re-check, UNIQUE conflict
295
- * detection — resolves names through it, so a collation registered with
296
- * `db.registerCollation` on *this* connection is honored rather than silently
297
- * degraded to BINARY by the process-global built-in registry.
298
- *
299
- * Only the resolver closure is cached: it reads the live registry, so a collation
300
- * registered after connect is still visible. Resolved *functions* are hoisted no
301
- * further than the comparator or constraint check that resolved them.
302
- *
303
- * Its key-bytes counterpart, `db.getKeyNormalizerResolver()`, is bound into
304
- * {@link encodeOptions} so the names in {@link pkKeyCollations} resolve to the same
305
- * per-connection registry.
306
- */
307
- protected readonly collationResolver: CollationResolver;
308
- protected ddlSaved: boolean;
309
- protected cachedStats: TableStats | null;
310
- protected pendingStatsDelta: number;
311
- protected mutationCount: number;
312
- protected statsFlushPending: boolean;
313
- private readonly predicateCache;
314
- private readonly indexPredicateCache;
315
- /**
316
- * {@link tableSchema} with a hidden `_uc_*` secondary index materialized per
317
- * non-derived UNIQUE constraint (see {@link withImplicitUniqueIndexes}) — the
318
- * schema that drives UNIQUE ENFORCEMENT ({@link findIndexForUniqueConstraint})
319
- * and physical index MAINTENANCE ({@link updateSecondaryIndexes}).
320
- *
321
- * Deliberately SEPARATE from the engine-facing {@link tableSchema}: the engine
322
- * reads `tableInstance.tableSchema` at CREATE (`SchemaManager.finalizeCreatedTableSchema`)
323
- * and the read-query planner reads the registered schema, and NEITHER may see
324
- * `_uc_*` (mirrors the memory backend keeping its materialized schema
325
- * manager-local; a plain UNIQUE gets no read-side plan from `_uc_*`, only
326
- * enforcement). Recomputed alongside `tableSchema` in the constructor and
327
- * {@link updateSchema}.
328
- */
329
- private materializedSchema;
330
- constructor(db: Database, storeModule: StoreTableModule, tableSchema: TableSchema, config: StoreTableConfig, eventEmitter?: StoreEventEmitter, isConnected?: boolean);
331
- /**
332
- * Reject, at DDL time, any collation this table's key encoding would need but cannot
333
- * use — before a single row is written under bytes the connection cannot reproduce.
334
- *
335
- * A collation keys a persisted structure only if it carries a KEY NORMALIZER (the
336
- * `(s) => string` whose output equality partitions strings exactly as its comparator
337
- * does). `db.registerCollation(name, cmp)` with no `{ normalizer }` gives a collation
338
- * that can order rows but not key them; an unregistered name gives nothing at all.
339
- * Both raise here rather than at the first insert.
340
- *
341
- * Checked over exactly the collations the key encoding actually uses:
342
- * - every defined `pkKeyCollations` entry (text-capable PK columns);
343
- * - the table key collation K, but only when it is reachable — a secondary index
344
- * encodes a TEXT-CAPABLE index column's bytes under K (`buildIndexKey`).
345
- *
346
- * So neither a table with an integer PK and no secondary index, nor one whose every
347
- * index column is type-natively keyed, is made unopenable by a K it never encodes with.
348
- *
349
- * Blast radius: this also fires on catalog rehydration. Reopening a persisted database
350
- * from a connection that has not re-registered its custom collation now throws at
351
- * CREATE-TABLE-from-catalog rather than silently reading rows under a key layout it
352
- * cannot reproduce. See `docs/plugins.md`.
353
- *
354
- * Takes `pkKeyCollations` rather than reading the field so `updateSchema` can validate
355
- * the incoming schema BEFORE it overwrites its own state with it.
356
- */
357
- private validateKeyCollations;
358
- /**
359
- * Reject, at DDL time, a key member whose semantic-ordering logical type has no
360
- * store key transform — before a single row is keyed under bytes whose order the
361
- * type's `compare` (and so the isolation merge, the memory oracle, and Sort)
362
- * disagrees with.
363
- *
364
- * A semantic-ordering type keys soundly only through an order- and
365
- * identity-preserving transform ({@link storeSemanticKeyTransform}: the type's
366
- * `groupKey`, or a store-local encoder like JSON's structural one). This gap used
367
- * to be undetectable — JSON sat here transform-less for a release, keying in
368
- * canonical-text order while every comparator ordered structurally, and the only
369
- * symptom was a misaligned overlay merge deep inside a transaction. Unreachable
370
- * today (TIMESPAN and JSON are both covered); it exists so the NEXT
371
- * semantic-ordering type fails loudly at CREATE TABLE instead.
372
- *
373
- * Checked over the same materialized schema as {@link validateKeyCollations}:
374
- * every PK member and every (explicit or hidden `_uc_*`) index column.
375
- *
376
- * CAVEAT: this can only check that a transform EXISTS — a `groupKey` that is
377
- * identity-faithful but not order-preserving passes undetected, so a new type's
378
- * transform still needs its byte order argued against its `compare` (see
379
- * json-key.ts for the shape of that argument).
380
- */
381
- private validateSemanticKeyTransforms;
382
- /** Get the table configuration. */
383
- getConfig(): StoreTableConfig;
384
- /** Get the table schema (engine-facing, WITHOUT the hidden `_uc_*` indexes). */
385
- getSchema(): TableSchema;
386
- /**
387
- * Get the enforcement schema — {@link getSchema} plus the hidden `_uc_*`
388
- * index materialized per non-derived UNIQUE. Used by the module to resolve the
389
- * physical index store to build/tear down during ALTER; never registered with
390
- * the engine.
391
- */
392
- getMaterializedSchema(): TableSchema;
393
- /**
394
- * Update the table schema after an ALTER TABLE / CREATE INDEX operation.
395
- *
396
- * Validates the incoming schema's key collations before adopting any of it, so a
397
- * rejection leaves this table on its previous, consistent schema.
398
- */
399
- updateSchema(newSchema: TableSchema): void;
400
- /**
401
- * Mark the table's DDL as already persisted to the catalog, so the lazy
402
- * first-store-access save in {@link initializeStore} is skipped. Called by
403
- * `StoreModule.createIndex` / `dropIndex` after they eagerly write the catalog
404
- * bundle, so a subsequent INSERT does not redundantly re-persist identical DDL.
405
- */
406
- markDdlSaved(): void;
407
- /** Close and forget a cached index-store handle, if any. */
408
- releaseIndexStore(indexName: string): Promise<void>;
409
- /**
410
- * Returns true if the table has at least one LIVE row — the open transaction's
411
- * pending overlay over the committed store. Stops after the first hit.
412
- *
413
- * Effective (not committed-only) because its one caller, `ADD COLUMN`'s NOT
414
- * NULL-without-DEFAULT rejection, must see rows the same transaction inserted:
415
- * they are migrated too (the arm DDL-commits before `migrateRows`), and a
416
- * committed-only probe would wave them through only to give them a NULL in a
417
- * NOT NULL column. Conversely, a pending delete of the last row makes the table
418
- * legitimately empty. Reading effectively keeps the check throw-only and
419
- * BEFORE the DDL-commit, so a rejection leaves the transaction intact.
420
- */
421
- hasAnyRows(): Promise<boolean>;
422
- /**
423
- * Yield the LIVE value of `colIndex` for every row — the open transaction's
424
- * pending overlay over the committed store. For throw-only ALTER COLUMN probes
425
- * that must run BEFORE the arm's DDL-commit (see `StoreModule.ddlCommitPendingOps`):
426
- * they have to see rows this transaction wrote, because those rows are rewritten
427
- * too, and a rejection here must leave the transaction intact.
428
- */
429
- iterateEffectiveValuesAtIndex(colIndex: number): AsyncIterable<SqlValue>;
430
- /**
431
- * Count the LIVE rows whose column at `colIndex` holds NULL (see
432
- * {@link iterateEffectiveValuesAtIndex} for why this reads effectively).
433
- * Used by ALTER COLUMN SET NOT NULL to decide whether the tightening is safe.
434
- */
435
- rowsWithNullAtIndex(colIndex: number): Promise<number>;
41
+ export declare class StoreTable extends StoreTableConstraints {
436
42
  /**
437
43
  * Apply a per-row mapping function to every stored row, in place (re-writing
438
44
  * the same key). The mapper may throw QuereusError — propagated to the caller;
@@ -446,6 +52,64 @@ export declare class StoreTable extends VirtualTable {
446
52
  * over the rewritten store at commit.
447
53
  */
448
54
  mapRowsAtIndex(colIndex: number, mapper: (value: SqlValue) => SqlValue): Promise<void>;
55
+ /**
56
+ * Closure computing a row's data key under a NEW primary-key definition and
57
+ * column set — the exact bytes {@link rekeyRows} pass 2 writes. Shared by
58
+ * `rekeyRows`' two passes and {@link validateRekeyedPrimaryKey}'s probes so a
59
+ * collision judged by a probe is byte-identical to the key the re-key would
60
+ * write. Deliberately NOT the `dedupeRowSignature` / `KeyNormalizerResolver`
61
+ * path the UNIQUE validators use — the two disagree for at least an `any`-typed
62
+ * PK member, whose key bytes pin BINARY regardless of its declared collation
63
+ * (pinned by `any-json-pk-binary-key.spec.ts`).
64
+ *
65
+ * The transforms matter too: an ALTER PRIMARY KEY onto (or a SET DATA TYPE
66
+ * creating) a semantic-ordering member must collapse equal spellings, so
67
+ * 'PT1H'/'PT60M' land on one key and are judged as the duplicate they are.
68
+ */
69
+ private rekeyedKeyComputer;
70
+ /**
71
+ * The store's counterpart of the memory backend's
72
+ * `MemoryTableManager.validateRekeyedPrimaryKey`: the two throw-only questions a
73
+ * PK re-key must answer, over two DIFFERENT row sets, BEFORE anything is flushed
74
+ * or mutated (see docs/memory-table.md §"A collation change on a PRIMARY KEY
75
+ * column obeys a stricter rule"):
76
+ *
77
+ * 1. **Is the change legal?** — over `effectiveRows`, the rows the DDL-issuing
78
+ * transaction can SEE (a wrapper's `EffectiveRowSource` when the isolation
79
+ * layer holds the transaction's staged rows outside this store, else this
80
+ * table's own effective stream). Two rows on one new key here is a duplicate
81
+ * a `select` in this transaction would return, so the change is invalid →
82
+ * `CONSTRAINT`, naming the colliding key.
83
+ * 2. **Can the store carry it?** — over this store's COMMITTED rows, the set a
84
+ * `rollback` must be able to restore. The data store holds one row per key,
85
+ * so a committed pair collapsing onto one new key cannot be represented even
86
+ * when the transaction has deleted one of them → `BUSY`, with the memory
87
+ * module's "commit/rollback and retry" posture.
88
+ *
89
+ * Probe order is what makes the statuses right, with no backend sniffing: the
90
+ * committed probe can only fire once the effective one passed, i.e. only when the
91
+ * committed rows are NOT a subset of the effective ones — which happens exactly
92
+ * when the transaction has DELETED a committed row (staged in a wrapper's overlay,
93
+ * or buffered in this module's own coordinator). A collision among rows the
94
+ * transaction can still see therefore always reports `CONSTRAINT`, never `BUSY`.
95
+ *
96
+ * The buffered-delete case makes the bare module stricter than it used to be: the
97
+ * old post-flush pass committed the delete first and then re-keyed happily, quietly
98
+ * spending the transaction's rollback. It now refuses with the same `BUSY` the
99
+ * wrapped path gives, and `commit; <retry>` still lands the change.
100
+ *
101
+ * Both probes key through {@link rekeyedKeyComputer}, so they and the re-key agree
102
+ * byte-for-byte.
103
+ *
104
+ * NOTE: these two probes put the SET COLLATE re-key at four full table scans (two
105
+ * here, then {@link rekeyRows}' pass 1 backstop and pass 2), each holding one hex
106
+ * key signature per row. Fine for a statement this rare; if a huge table ever makes
107
+ * it slow, drop pass 1 for callers that pre-validated — it cannot fire for them.
108
+ */
109
+ validateRekeyedPrimaryKey(newPkDef: ReadonlyArray<{
110
+ index: number;
111
+ desc?: boolean;
112
+ }>, newColumns: ReadonlyArray<ColumnSchema>, effectiveRows: AsyncIterable<Row>): Promise<void>;
449
113
  /**
450
114
  * Re-key every stored row under a new primary-key definition.
451
115
  *
@@ -453,7 +117,11 @@ export declare class StoreTable extends VirtualTable {
453
117
  * key and retains only a `Set` of key SIGNATURES (hex of the key bytes) to detect
454
118
  * collisions — two distinct old keys collapsing to one new key under a coarser
455
119
  * collation or a narrower PK. On collision we throw `CONSTRAINT` without touching
456
- * the store. The second pass RE-SCANS the same committed store, recomputes each
120
+ * the store. For `ALTER PRIMARY KEY` this pass is the gate; for `ALTER COLUMN …
121
+ * SET COLLATE` on a PK member it is only a backstop — that arm has already run
122
+ * {@link validateRekeyedPrimaryKey}'s two probes before the DDL flush, so a
123
+ * refusal there leaves the enclosing transaction alive. The second pass RE-SCANS
124
+ * the same committed store, recomputes each
457
125
  * new key, and batches deletes of displaced old keys + puts of new (key, row)
458
126
  * pairs into ONE atomic batch. Rows whose new key matches the old key are no-ops.
459
127
  *
@@ -504,575 +172,8 @@ export declare class StoreTable extends VirtualTable {
504
172
  notNull: boolean;
505
173
  columnName: string;
506
174
  }): Promise<void>;
507
- /**
508
- * Ensure the data store is open and DDL is persisted.
509
- * Uses a promise-based singleton pattern to prevent race conditions
510
- * when multiple concurrent queries access the same table.
511
- */
512
- protected ensureStore(): Promise<KVStore>;
513
- /**
514
- * Internal method to actually initialize the store.
515
- * Only called once per table instance.
516
- */
517
- private initializeStore;
518
- /**
519
- * Get or create an index store for the given index name.
520
- */
521
- protected ensureIndexStore(indexName: string): Promise<KVStore>;
522
- /**
523
- * Get or create the stats store.
524
- */
525
- protected ensureStatsStore(): Promise<KVStore>;
526
- /**
527
- * Resolve + cache the module's shared TransactionCoordinator and hook the
528
- * stats lifecycle callbacks, WITHOUT creating or registering a connection.
529
- * Synchronous (the coordinator is handle-addressed, never store-opening — see
530
- * `StoreTableModule.getCoordinator`), so the backing host's resolution path can
531
- * call it eagerly. Attaching matters for reads: `iterateEffective` /
532
- * `readEffectiveRowByKey` consult `this.coordinator` for the pending merge, so a
533
- * table whose only writer is the privileged backing host (which queues ops on the
534
- * module coordinator, never through `update()`) must still hold the
535
- * reference for its read paths to be reads-own-writes.
536
- *
537
- * The coordinator is module-wide, so its commit/rollback callback array holds
538
- * one {stats apply/discard} pair per PARTICIPATING table; each table's
539
- * `applyPendingStats` early-returns when its own `pendingStatsDelta` is 0, so a
540
- * table that did no work contributes nothing. Registers only on first call per
541
- * StoreTable instance (the `if (!this.coordinator)` guard); a fresh instance
542
- * after drop+recreate re-registers against the shared coordinator. The OLD
543
- * instance is not GC'd on its own — its callback closures capture `this` and
544
- * stay pinned on the module-wide coordinator until {@link dispose} runs the
545
- * captured disposer (called at the genuine eviction sites, see
546
- * `StoreModule.tearDownTableStorage` / `renameTable`).
547
- */
548
- attachCoordinator(): TransactionCoordinator;
549
- /**
550
- * Ensure the coordinator is available and connection is registered.
551
- */
552
- protected ensureCoordinator(): Promise<TransactionCoordinator>;
553
- /** Apply pending stats on commit. */
554
- protected applyPendingStats(): void;
555
- /** Discard pending stats on rollback. */
556
- protected discardPendingStats(): void;
557
- /** Flush statistics to the stats store. */
558
- protected flushStats(): Promise<void>;
559
- /** Create a new connection for transaction support. */
560
- createConnection(): Promise<VirtualTableConnection>;
561
- /** Get the current connection. */
562
- getConnection(): VirtualTableConnection | undefined;
563
- /** Extract primary key values from a row. */
564
- protected extractPK(row: Row): SqlValue[];
565
- /**
566
- * Coerce each cell in `row` to its declared column logical type.
567
- * Mirrors the memory-table path (MemoryTableManager.performInsert/performUpdate)
568
- * so INTEGER/REAL affinity is applied and JSON columns are parsed into native
569
- * objects before PK extraction, serialization, and index-key construction.
570
- */
571
- protected coerceRow(row: Row): Row;
572
- /**
573
- * Query the table with optional filters.
574
- *
575
- * All three access arms read the EFFECTIVE row state: the committed store
576
- * merged with this table's coordinator's pending ops when a transaction is
577
- * active (read-your-own-writes — see {@link iterateEffective} /
578
- * {@link readLiveRowByPk}). Merged emission stays in encoded-PK-key order,
579
- * preserving the module's `providesOrdering` / `monotonicOn` advertisements.
580
- */
581
- query(filterInfo: FilterInfo): AsyncIterable<Row>;
582
- /**
583
- * Iterate the effective entry state of this table's data store within
584
- * `bounds`: the committed `store.iterate` stream merged with the
585
- * coordinator's pending ops for the data store (read-your-own-writes).
586
- *
587
- * Both inputs are sorted by encoded key bytes, so this is an
588
- * order-preserving two-way merge: a pending put wins over a committed entry
589
- * at the same key, a pending delete suppresses its committed entry, and
590
- * pending puts outside `bounds` are excluded. With no active transaction
591
- * (or an empty pending bucket) it degrades to the bare committed iterate.
592
- *
593
- * The pending side is the module coordinator's bucket for THIS table's data
594
- * store handle (`store`) — every data op is queued under that exact handle, so
595
- * a sibling table's pending ops (a different data-store handle) never bleed in.
596
- */
597
- protected iterateEffective(store: KVStore, bounds: IterateOptions, reverse?: boolean): AsyncIterable<KVEntry>;
598
- /**
599
- * True when a byte window over the LEADING PK column reproduces the comparator's order
600
- * — the precondition {@link buildPKRangeBounds} needs, and the exact condition
601
- * `StoreModule.computeBestAccessPlan` uses before claiming the range filters handled.
602
- * When false, the range arm degrades to a full scan + `matchesFilters` residual.
603
- */
604
- protected leadingPkRangeIsOrderSafe(): boolean;
605
- /**
606
- * True when some PK member's logical type carries semantic ordering (TIMESPAN,
607
- * JSON). Such PKs decline the point arm and full-scan, where
608
- * {@link matchesFilters} applies the type's compare. Historically required —
609
- * byte-key equality was a strict subset of the type's equality ('PT1H' ≡
610
- * 'PT60M' elapsed-time-equal, byte-distinct) and a point window under-fetched
611
- * with no residual able to resurrect a skipped row. Both types' key transforms
612
- * (see {@link storeSemanticKeyTransform}) now make byte equality the type's
613
- * equality, so the decline is conservative; re-opening is tracked in backlog
614
- * `feat-reopen-timespan-store-seeks`. (Range windows and ordering
615
- * advertisements are already declined by {@link keyOrderMatchesCollation}.)
616
- */
617
- protected pkHasSemanticOrderingMember(): boolean;
618
- /** Analyze filter info to determine PK access pattern. */
619
- protected analyzePKAccess(filterInfo: FilterInfo): PKAccessPattern;
620
- /**
621
- * Convert a leading-PK-column range access into one seek + early-terminate
622
- * iterate window.
623
- *
624
- * Each LT/LE/GT/GE constraint's bound value is encoded under the SAME
625
- * per-column DESC direction and key collation as the data keys (via
626
- * {@link encodePkPrefixBounds}), giving the byte region `[lo, hi)` whose
627
- * leading column equals that value (`lo = encode([x])`,
628
- * `hi = incrementLastByte(lo)`). The op maps that region's endpoints onto a
629
- * `gte`/`lt` window; because a DESC column bit-inverts its bytes (larger value
630
- * ⇒ smaller bytes), the lower/upper assignment swaps with direction:
631
- *
632
- * | op | ASC | DESC |
633
- * |----|----------|----------|
634
- * | GE | gte = lo | lt = hi |
635
- * | GT | gte = hi | lt = lo |
636
- * | LE | lt = hi | gte = lo |
637
- * | LT | lt = lo | gte = hi |
638
- *
639
- * Across constraints (BETWEEN ⇒ one lower + one upper; a redundant same-side
640
- * pair ⇒ the tighter wins) we keep the MAX lower candidate for `gte` and the
641
- * MIN upper candidate for `lt`. A candidate that resolves to `undefined` (an
642
- * `hi` whose increment overflowed all-0xff) leaves that side unbounded — a safe
643
- * SUPERSET, since {@link matchesFilters} stays the authoritative collation-aware
644
- * row filter. A NULL/missing bound value is likewise skipped (the planner never
645
- * pushes `= NULL`, and a range op against NULL rejects every row in matchesFilters).
646
- *
647
- * NOTE: a range window over a text PK column is sound only when the column's key
648
- * normalizer is ORDER-preserving with respect to its comparator — i.e. the comparator
649
- * orders two strings the way memcmp orders their normalized bytes. That is NOT implied
650
- * by `db.registerCollation`'s normalizer contract, which promises only an equality
651
- * partition, and a built-in NAME may be re-registered with a comparator + normalizer
652
- * pair that preserves equality while inverting order. This method is therefore only
653
- * ever reached under {@link leadingPkRangeIsOrderSafe} — enforced by
654
- * {@link analyzePKAccess} here and mirrored by `StoreModule.computeBestAccessPlan`
655
- * before it claims the range filters handled. A collation without the
656
- * `orderPreserving` assertion costs the seek, never a row.
657
- */
658
- protected buildPKRangeBounds(access: PKAccessPattern): IterateOptions;
659
- /**
660
- * Scan a leading-PK-column range, seeking to the window start and
661
- * early-terminating at its end.
662
- *
663
- * {@link buildPKRangeBounds} converts the LT/LE/GT/GE constraints into one
664
- * encoded-byte `gte`/`lt` window under the same per-column DESC directions and
665
- * key collations the data keys use, so the iterate visits a SUPERSET of the
666
- * qualifying rows (a collation widening or the bound-byte increment can
667
- * over-fetch, never under-fetch). {@link matchesFilters} stays the authoritative
668
- * collation-aware row filter. {@link iterateEffective} restricts the pending
669
- * merge to the same `bounds`, so read-your-own-writes holds on the narrowed window.
670
- */
671
- protected scanPKRange(store: KVStore, access: PKAccessPattern, filterInfo: FilterInfo): AsyncIterable<Row>;
672
- /**
673
- * Resolve the secondary index chosen by the planner from `filterInfo.idxStr`.
674
- *
675
- * The planner emits `idx=<name>(<n>);plan=…` when its access plan set both an
676
- * `indexName` and `seekColumnIndexes` (see `getBestAccessPlan` and
677
- * rule-select-access-path.ts). Decoding goes through the engine's shared
678
- * `decodeIdxStr`, so the store, the in-memory vtab, and the isolation overlay all
679
- * read one format. Returns null for the PK/scan sentinels (`_primary_`, `fullscan`,
680
- * `empty`), an idxStr that names no index, or a name absent from `schema.indexes` —
681
- * every one of which routes back to a PK/full-scan arm.
682
- */
683
- protected resolveIndexFromIdxStr(idxStr: string | null): TableIndexSchema | null;
684
- /**
685
- * Analyze filter info to determine a secondary-index access pattern, mirroring
686
- * {@link analyzePKAccess} but over the index chosen in `idxStr`.
687
- *
688
- * A contiguous leading-prefix EQ on the index columns yields a `point` window
689
- * (the prefix covers every entry sharing those leading values — an index seek
690
- * is a PREFIX scan, not a single row, since the index need not be unique and
691
- * the PK suffix varies); otherwise a range (LT/LE/GT/GE) on the LEADING index
692
- * column yields a `range` window. Returns null when neither applies or when the
693
- * index is unresolved. {@link matchesFilters} stays the authoritative row filter,
694
- * so the window need only be a SUPERSET.
695
- *
696
- * Index-column bytes are encoded under the table key collation K (NOT the index's
697
- * per-column declared collation — see `buildIndexKey`), while `matchesFilters` compares
698
- * under the index column's effective collation C. The EQ/prefix window is a superset
699
- * whenever K is coarser-or-equal to C, which `StoreModule.tryIndexAccessPlan` checked
700
- * before it named this index. The RANGE window additionally needs byte order to BE
701
- * comparator order, so it is gated on {@link keyOrderMatchesCollation} (which demands
702
- * `C === K` plus K's `orderPreserving` assertion); when that fails we return null and
703
- * the caller full-scans.
704
- */
705
- protected analyzeIndexAccess(filterInfo: FilterInfo): IndexAccessPattern | null;
706
- /**
707
- * True when a byte window over `leadingCol` of `index` reproduces the comparator's
708
- * order. Mirrors the range arm of `StoreModule.tryIndexAccessPlan`: the window's bytes
709
- * come from the table key collation K, the residual compares under the index column's
710
- * effective collation C, so both must be the same order-preserving collation.
711
- */
712
- protected indexRangeIsOrderSafe(index: TableIndexSchema, leadingCol: number): boolean;
713
- /**
714
- * Convert leading-index-column LT/LE/GT/GE constraints into one encoded-byte
715
- * `gte`/`lt` window — the secondary-index analogue of {@link buildPKRangeBounds}.
716
- *
717
- * Each bound value is encoded under {@link encodeOptions} (the table key collation K
718
- * and its normalizer resolver) and the leading index column's DESC `dir` — exactly as
719
- * {@link buildIndexKey} encodes
720
- * that column — via {@link buildIndexPrefixBounds}, giving the byte region
721
- * `[lo, hi)` whose leading column equals that value. The op maps that region's
722
- * endpoints onto `gte`/`lt`, with the same DESC lower/upper SWAP as the PK path:
723
- *
724
- * | op | ASC | DESC |
725
- * |----|----------|----------|
726
- * | GE | gte = lo | lt = hi |
727
- * | GT | gte = hi | lt = lo |
728
- * | LE | lt = hi | gte = lo |
729
- * | LT | lt = lo | gte = hi |
730
- *
731
- * Across constraints keep the MAX lower and MIN upper. An `undefined` upper (an
732
- * `hi` whose increment overflowed all-0xff) leaves that side unbounded — a safe
733
- * SUPERSET. A NULL/missing bound value is skipped (the planner never pushes
734
- * `= NULL`, and a range op against NULL rejects every row in matchesFilters).
735
- */
736
- protected buildIndexRangeBounds(constraints: Array<{
737
- op: IndexConstraintOp;
738
- value?: SqlValue;
739
- }>, dir: boolean): IterateOptions;
740
- /**
741
- * Scan a secondary index over `access.bounds`, resolving each index entry to its
742
- * base row and re-filtering.
743
- *
744
- * {@link iterateEffective} yields the committed index entries merged with this
745
- * transaction's pending index puts/deletes (read-your-own-writes over the
746
- * index), in index-key byte order — the order the isolation overlay merge relies
747
- * on (`isolated-table.ts` § buildSortKey). We resolve each entry to its row via
748
- * its stored data-key value WITHOUT reordering, so index-key order is preserved.
749
- *
750
- * Defense in depth mirroring the memory layer's live-recheck: a resolved-null
751
- * row (the entry's row was deleted — a pending index delete would normally
752
- * suppress the entry, but a committed entry can lag) is skipped, and every
753
- * resolved row is re-checked by {@link matchesFilters} (the byte window is only
754
- * a superset, and a stale entry whose indexed column no longer matches is
755
- * dropped).
756
- */
757
- protected scanIndex(indexStore: KVStore, access: IndexAccessPattern, filterInfo: FilterInfo): AsyncIterable<Row>;
758
- /**
759
- * Check if a row matches the filter constraints.
760
- *
761
- * `collations` maps a constrained column index to the comparison function that
762
- * column must be re-checked under; every caller builds it once per scan with
763
- * {@link resolveFilterCollations}, whose doc comment explains how the name is
764
- * chosen. A column absent from the map compares BINARY — the same result
765
- * {@link resolveFilterCollations} produces for an undeclared collation, and the
766
- * only reachable absence, since both walk the constraint list under identical
767
- * skip conditions.
768
- */
769
- protected matchesFilters(row: Row, filterInfo: FilterInfo, collations: ReadonlyMap<number, CollationFunction>): boolean;
770
- /**
771
- * Resolve, ONCE per scan, the comparison collation function for every pushed
772
- * constraint column, keyed by column index.
773
- *
774
- * The name for a column is its DECLARED collation (absent ⇒ BINARY) — the same
775
- * resolution the access path's collation-cover analysis uses
776
- * (indexColumnCollationLookup / primaryKeyCollationLookup) when it decides a
777
- * pushed constraint is fully covered, and the same source this file's UNIQUE
778
- * checks compare under. On a collation MATCH the planner drops the residual
779
- * Filter, so {@link matchesFilters} alone must reproduce the predicate.
780
- *
781
- * `indexCollationNames` (from {@link indexColumnCollations}) overrides the
782
- * declared name for the secondary-index scan arm: the planner's MATCH there is
783
- * against the INDEX column's collation, which an explicit `COLLATE` on the index
784
- * can make differ from the table column's.
785
- *
786
- * Names resolve against {@link collationResolver}, so an unregistered collation
787
- * raises `no such collation sequence` at scan setup rather than byte-ordering
788
- * every row.
789
- *
790
- * NOTE: rebuilt on every `query()` / `scanPKRange()` / `scanIndex()` call — one
791
- * registry lookup per distinct constrained column, dwarfed by the scan's I/O. If a
792
- * point-lookup-heavy profile ever shows it, memoize on the `FilterInfo`.
793
- */
794
- protected resolveFilterCollations(filterInfo: FilterInfo, indexCollationNames?: ReadonlyMap<number, string | undefined>): ReadonlyMap<number, CollationFunction>;
795
- /**
796
- * Effective per-column comparison collation for a secondary index's columns:
797
- * the index column's own `COLLATE` when present, else the underlying table
798
- * column's declared collation. Mirrors the resolution `StoreModule`'s
799
- * index-maintenance UNIQUE dedup uses (`indexCol.collation ?? tableColumn`), so
800
- * a re-checked index-scan row compares under the same collation the planner used
801
- * to justify dropping (or keeping) the residual Filter.
802
- */
803
- protected indexColumnCollations(index: TableIndexSchema): Map<number, string | undefined>;
804
- /**
805
- * Compare two values according to an operator, under `collationFunc` (already
806
- * resolved by {@link resolveFilterCollations} against this database's collation
807
- * registry). So the LT/LE/GT/GE range bounds honour a NOCASE/RTRIM/custom column
808
- * collation rather than a raw BINARY JS comparison — the capability
809
- * `StoreModule.getBestAccessPlan` advertises via `honorsCollatedRangeBounds`.
810
- * NULL on either side fails every operator except EQ-with-both-NULL (the
811
- * internal point-lookup convention; the planner never pushes `= NULL`).
812
- */
813
- protected compareValues(a: SqlValue, op: IndexConstraintOp, b: SqlValue, collationFunc: CollationFunction, semanticCompare?: (a: SqlValue, b: SqlValue) => number): boolean;
814
175
  /** Perform an update operation (INSERT, UPDATE, DELETE). */
815
176
  update(args: UpdateArgs): Promise<UpdateResult>;
816
- /**
817
- * Update secondary indexes after a row change.
818
- *
819
- * For PK-change UPDATE, `oldPk` (where the existing entry lives) and `newPk`
820
- * (where the relocated entry will live) differ; using a single pk for both
821
- * sides leaks the old entry. Other paths pass the same pk for both.
822
- */
823
- protected updateSecondaryIndexes(inTransaction: boolean, oldRow: Row | null, newRow: Row | null, oldPk: SqlValue[], newPk?: SqlValue[]): Promise<void>;
824
- /**
825
- * Returns the compiled predicate for a partial-UNIQUE constraint, or undefined
826
- * when the constraint covers the full table. Compilation is memoized per
827
- * UniqueConstraintSchema instance so the hot UNIQUE-check path doesn't recompile.
828
- */
829
- private compileFor;
830
- /**
831
- * Returns the compiled predicate for a partial secondary index, or undefined
832
- * for a full index. Compilation is memoized per IndexSchema instance so the hot
833
- * DML index-maintenance path doesn't recompile.
834
- */
835
- private compileIndexFor;
836
- /**
837
- * Check if two PK arrays name the same row. A semantic-ordering member compares
838
- * through its type's `compare` (see {@link resolvePkSemanticEquality}) so
839
- * differently-spelled equal values ('PT1H' / 'PT60M') answer "same row",
840
- * agreeing with the physical key identity {@link encodeDataKey} produces; every
841
- * other member keeps the original strict `!==`.
842
- */
843
- protected keysEqual(a: SqlValue[], b: SqlValue[]): boolean;
844
- /**
845
- * Per-constrained-column comparison functions for one UNIQUE constraint: the
846
- * type's `compare` for a semantic-ordering column (so 'PT1H' conflicts with
847
- * 'PT60M', matching the memory backend's typed BTree), else the enforcement
848
- * collation the caller resolved (`resolveUniqueEnforcementCollations`) through
849
- * `compareSqlValuesFast` — the exact comparison every finder used before.
850
- *
851
- * Thin wrapper over the engine's `uniqueEnforcementComparators`, which memory
852
- * and the isolation overlay share so the three backends cannot drift.
853
- */
854
- private uniqueColumnComparators;
855
- /**
856
- * Returns true if any column covered by a UNIQUE constraint differs between
857
- * oldRow and newRow, or — for partial UNIQUE — any column referenced by the
858
- * partial predicate differs (which can transition the row across the
859
- * predicate scope and re-trigger the uniqueness check).
860
- */
861
- protected uniqueColumnsChanged(oldRow: Row, newRow: Row): boolean;
862
- /**
863
- * Enforce table-level UNIQUE constraints against the prospective newRow.
864
- * Honors `onConflict`: IGNORE returns an ok-with-undefined-row; REPLACE
865
- * deletes the conflicting row(s) and continues; otherwise returns a
866
- * constraint result. Returns null when all constraints pass.
867
- *
868
- * Rows whose PK is in `selfPks` are skipped (the row being inserted/updated).
869
- * NULL in any covered column skips that constraint (multiple NULLs are allowed
870
- * per SQL standard).
871
- *
872
- * Reads through the transaction coordinator's pending writes when active so
873
- * intra-transaction duplicates are detected.
874
- *
875
- * REPLACE evictions (rows at OTHER PKs) are deleted from storage and pushed onto
876
- * `evicted` so the DML executor runs the full delete pipeline for each
877
- * (change-tracking, row-time MV maintenance, FK cascade, auto-events).
878
- */
879
- protected checkUniqueConstraints(inTransaction: boolean, newRow: Row, selfPks: SqlValue[][], onConflict: ConflictResolution | undefined, evicted: Row[]): Promise<UpdateResult | null>;
880
- /**
881
- * Route one UNIQUE constraint's conflict search to the cheapest SOUND finder,
882
- * in descending preference:
883
- *
884
- * 1. A linked row-time covering MV — its backing table (hosted by any
885
- * backing-host-capable module — memory by default, this store module under
886
- * `using store` — queried through the db with reads-own-writes) answers the
887
- * uniqueness question, mirroring the memory enforcement path.
888
- * 2. A physical secondary index realizing the constraint — one prefix seek
889
- * instead of a full table scan (see {@link findIndexForUniqueConstraint}).
890
- * 3. The full data-store scan ({@link findUniqueConflict}) — always correct,
891
- * O(rows) per checked row.
892
- *
893
- * Every finder returns the SAME `{pk, row}` shape, so the caller's conflict
894
- * action (ABORT / IGNORE / REPLACE eviction) is finder-independent.
895
- */
896
- private findUniqueConflictFor;
897
- /**
898
- * The `schema.indexes` entry whose physical index store can serve `uc`'s
899
- * conflict search as a point seek, or undefined when none can.
900
- *
901
- * Every non-derived UNIQUE now carries a materialized implicit index
902
- * (`_uc_*`, see {@link withImplicitUniqueIndexes}), so — like the memory
903
- * backend — a plain column- or table-level `UNIQUE` is index-servable, not
904
- * only an explicit `CREATE INDEX`. A UC is index-servable when:
905
- *
906
- * - it is index-derived (`derivedFromIndex`, from `CREATE UNIQUE INDEX`) and
907
- * its named index is still present — the index's partial predicate then
908
- * equals the constraint's by construction (`appendIndexToTableSchema`); or
909
- * - some index's columns equal `uc.columns` positionally AND whose predicate
910
- * is the SAME object as `uc.predicate` (reference identity — the implicit
911
- * materializer sets it that way). For a FULL UNIQUE that is
912
- * `undefined === undefined`, which also admits any user full index over the
913
- * same columns. For a PARTIAL `unique(...) where p` it admits only the
914
- * co-scoped `_uc_*` (which holds exactly the in-scope rows); an arbitrary
915
- * user partial index with a DIFFERENT predicate object stays conservative
916
- * (declined → full scan), because it physically omits rows the constraint
917
- * still covers. The serving index need not be UNIQUE — a plain index over
918
- * the constrained columns still holds every in-scope row.
919
- *
920
- * Collation guard (see {@link indexSeekHonorsEnforcementCollation}) may still
921
- * reject the found index, which routes the check back to the full scan.
922
- */
923
- private findIndexForUniqueConstraint;
924
- /**
925
- * True when a point seek into the index realizing `uc` returns a SUPERSET of
926
- * the constraint's true conflict set — the only condition under which the
927
- * seek may replace the full scan.
928
- *
929
- * An index key's leading (index-column) bytes are encoded under the TABLE KEY
930
- * collation K (`buildIndexKey` passes `this.encodeOptions`), NOT the index's
931
- * declared per-column COLLATE and NOT the constraint's enforcement collation
932
- * C (`uniqueEnforcementCollations` — the index's per-column COLLATE for an
933
- * index-derived UC, else the declared column collation). A seek therefore
934
- * fetches exactly `{rows K-equal to newRow}` while the re-validation keeps
935
- * `{rows C-equal to newRow}`. Soundness needs
936
- * `{C-equal} ⊆ {K-equal}`, i.e. K must be COARSER-OR-EQUAL to C per column:
937
- *
938
- * - non-text column → its bytes are type-native, collation-independent: safe.
939
- * - C == K → the sets coincide: safe.
940
- * - K = NOCASE, C = BINARY → K strictly coarser: safe superset.
941
- * - otherwise (K = BINARY over C = NOCASE/RTRIM; K = NOCASE over C = RTRIM)
942
- * the seek UNDER-fetches and a real duplicate would be silently accepted:
943
- * reject, so the caller full-scans.
944
- *
945
- * Same direction and same admitted cases as the read-side guard in
946
- * `StoreModule.tryIndexAccessPlan`; conservative rather than exhaustive
947
- * (K = RTRIM over C = BINARY is provably safe but declined), which costs an
948
- * optimization, never correctness. The coarseness test is only sound for the
949
- * built-in names: a custom K equals a custom C only when the index column names
950
- * that same collation, and otherwise falls through to the full scan.
951
- */
952
- private indexSeekHonorsEnforcementCollation;
953
- /**
954
- * The index analogue of {@link findUniqueConflict}: seek the index realizing
955
- * `uc` at the point formed by `newRow`'s constrained-column values, and
956
- * re-validate each resolved candidate exactly as the full scan does.
957
- *
958
- * The seek encodes ALL of `uc.columns` (positionally aligned with the index's
959
- * columns, guaranteed by `appendIndexToTableSchema` / the column-set match in
960
- * {@link findIndexForUniqueConstraint}) as a leading prefix of the index key,
961
- * under the same key collation and per-column DESC directions
962
- * {@link updateSecondaryIndexes} used to write it. The remaining suffix is the
963
- * row's PK, so the window spans every entry sharing those column values.
964
- * {@link iterateEffective} merges this transaction's pending index puts/deletes
965
- * over the committed entries, giving read-your-own-writes; each entry resolves
966
- * to its LIVE row through the data key stored as the entry's value.
967
- *
968
- * The seek only narrows the CANDIDATE set to a superset (guaranteed by
969
- * {@link indexSeekHonorsEnforcementCollation}); the authoritative comparison is
970
- * the identical self-PK exclusion, per-column enforcement-collation compare,
971
- * and partial-predicate scope check the full scan performs. A partial index
972
- * already excludes out-of-scope rows physically — the predicate re-check is
973
- * kept as defense in depth.
974
- *
975
- * Returns {@link INDEX_UNUSABLE} rather than a (possibly wrong) answer when an
976
- * entry carries a legacy empty value.
977
- */
978
- private findUniqueConflictViaIndex;
979
- /**
980
- * Scan committed + pending data rows for a row matching `newRow` on
981
- * `uc.columns` whose PK is not in `selfPks`. For partial UNIQUE, candidates
982
- * whose row does not satisfy the predicate are skipped. Returns the first
983
- * match or null.
984
- */
985
- private findUniqueConflict;
986
- /**
987
- * Find a UNIQUE conflict through a linked row-time covering MV's backing table
988
- * (the store analogue of the memory `checkUniqueViaMaterializedView`). The
989
- * backing scan yields candidate conflicting **source** PKs (reads-own-writes via
990
- * the backing's coordinated connection); each is validated against the *live*
991
- * store row (committed + this transaction's pending overlay) so a backing entry
992
- * that lags a row deleted/updated internally this statement is skipped rather
993
- * than raised as a false conflict. Returns the first real conflict or null.
994
- */
995
- private findUniqueConflictViaCoveringMv;
996
- /**
997
- * Read the live row at `pk` — this transaction's pending overlay (a pending
998
- * delete ⇒ gone; a pending put ⇒ its value) shadowing the committed store.
999
- * Backs `query()`'s point-lookup arm and validates covering-MV conflict
1000
- * candidates against the source of truth.
1001
- */
1002
- private readLiveRowByPk;
1003
- /** Encode `pkValues` (in PK-definition order) exactly as the data store keys rows. */
1004
- encodeDataKey(pkValues: SqlValue[]): Uint8Array;
1005
- /**
1006
- * Byte bounds covering every data key whose leading PK columns equal
1007
- * `prefixValues` — encoded under the same per-column DESC directions and key
1008
- * collations as {@link encodeDataKey}, so seek + early-terminate addresses the
1009
- * exact slice. An empty prefix yields full-scan bounds.
1010
- */
1011
- encodePkPrefixBounds(prefixValues: SqlValue[]): {
1012
- gte: Uint8Array;
1013
- lt?: Uint8Array;
1014
- };
1015
- /**
1016
- * Effective (pending-over-committed) point read by encoded data key: a pending
1017
- * delete ⇒ null, a pending put ⇒ its value, else the committed store entry.
1018
- */
1019
- readEffectiveRowByKey(key: Uint8Array): Promise<Row | null>;
1020
- /**
1021
- * Effective ordered entry scan within `bounds` (see {@link iterateEffective}).
1022
- * Opens the data store first — which fires the lazy first-access `saveTableDDL`
1023
- * for a freshly created table, the catalog write a store-backed MV backing
1024
- * relies on to survive reopen.
1025
- */
1026
- iterateEffectiveEntries(bounds: IterateOptions, reverse?: boolean): AsyncIterable<KVEntry>;
1027
- /** Buffer a privileged row-count delta, applied at coordinator commit (host writes). */
1028
- trackPrivilegedMutation(delta: number): void;
1029
- /**
1030
- * Declared secondary-UNIQUE enforcement for maintenance writes — the store
1031
- * mirror of the memory manager's `enforceSecondaryUniqueOnMaintenance` (see
1032
- * `vtab/backing-host.ts` § Constraint validation for the contract and
1033
- * docs/mv-constraints.md § Derived-row constraint validation for the
1034
- * semantics). Called by `StoreBackingHost.applyMaintenance` AFTER the op
1035
- * batch lands in the coordinator's pending state: post-batch is load-bearing
1036
- * (a `replace-all` diff applies puts before deletes, so a per-op check would
1037
- * false-positive when the derived set moves a unique value between primary
1038
- * keys), and checking only the WRITTEN images is complete (pre-existing
1039
- * contents already satisfied the constraint).
1040
- *
1041
- * Reuses {@link findUniqueConflict} — pending-overlay reads, per-column
1042
- * collations, NULL-pass, partial-predicate scope, self-PK exclusion — with
1043
- * the covering-MV route deliberately bypassed: a covering MV over THIS table
1044
- * is cascade-maintained only after the batch returns, so it lags the batch
1045
- * and would miss a same-batch colliding pair. The conflict action is a hard
1046
- * abort (a derivation write carries no user OR clause, and a declared
1047
- * `on conflict replace`/`ignore` default must not evict or drop derived
1048
- * rows). Per-image cost is one effective full scan: unlike the DML path
1049
- * ({@link findUniqueConflictFor}), this one is NOT routed through
1050
- * {@link findUniqueConflictViaIndex}, because a backing table keeps no
1051
- * secondary indexes by design — there is never an index store to seek.
1052
- *
1053
- * Zero overhead when the table declares no secondary UNIQUE (every MV-sugar
1054
- * backing, and most maintained tables): one empty-array check.
1055
- */
1056
- enforceSecondaryUniqueForMaintenance(changes: readonly BackingRowChange[]): Promise<void>;
1057
- /**
1058
- * Reset statistics to an absolute committed row count and flush immediately.
1059
- * Used by the host's `replaceContents` (create-fill / refresh), where the new
1060
- * count is exact, replacing any drifted delta-tracked estimate.
1061
- */
1062
- resetStats(rowCount: number): Promise<void>;
1063
- /**
1064
- * Open (and cache) the data store, firing the lazy first-access `saveTableDDL`.
1065
- * Public for the host's `replaceContents`, which writes the store directly.
1066
- */
1067
- openDataStore(): Promise<KVStore>;
1068
- /**
1069
- * Effective (pending-over-committed) point read by PK values — the public
1070
- * read an external writer issues before an upsert to learn the row's current
1071
- * image. Thin wrapper over the same private point-lookup that backs
1072
- * {@link query}'s point arm, so an external read merges pending-over-committed
1073
- * exactly like an engine read does.
1074
- */
1075
- readRowByPk(pk: SqlValue[]): Promise<Row | null>;
1076
177
  /**
1077
178
  * Apply externally-originated row ops directly to this source table's
1078
179
  * COMMITTED storage: table-owned data-key put/delete, secondary-index
@@ -1104,79 +205,5 @@ export declare class StoreTable extends VirtualTable {
1104
205
  * posture the prior raw-KV sync adapter took — not a regression, now stated.
1105
206
  */
1106
207
  applyExternalRowChanges(ops: readonly ExternalRowOp[]): Promise<BackingRowChange[]>;
1107
- /**
1108
- * Fully delete the row at `pk` (data + secondary indexes + stats + delete event).
1109
- * Used by REPLACE conflict resolution to evict a conflicting unique row before
1110
- * the caller's insert/update proceeds.
1111
- */
1112
- private deleteRowAt;
1113
- /**
1114
- * Begin a table-scoped transaction by ensuring the coordinator is active.
1115
- *
1116
- * Used by the isolation layer's flush path, which treats the underlying
1117
- * write as an independent mini-transaction. Idempotent: if the coordinator
1118
- * is already in a transaction (e.g. started by a registered connection),
1119
- * this is a no-op.
1120
- */
1121
- begin(): Promise<void>;
1122
- /**
1123
- * Commits a table-scoped transaction, flushing any buffered writes to the KV store.
1124
- * No-op if the coordinator is not currently in a transaction.
1125
- */
1126
- commit(): Promise<void>;
1127
- /**
1128
- * Rolls back a table-scoped transaction, discarding any buffered writes.
1129
- * No-op if the coordinator is not currently in a transaction.
1130
- */
1131
- rollback(): Promise<void>;
1132
- /** Disconnect from the store. */
1133
- disconnect(): Promise<void>;
1134
- /**
1135
- * Hard teardown: fully detach this instance from the module-wide coordinator.
1136
- *
1137
- * Distinct from the per-scan {@link disconnect} (which only flushes stats and
1138
- * deliberately keeps the table hooked mid-life). Called ONLY at the genuine
1139
- * eviction sites — `StoreModule.tearDownTableStorage` (drop / reclaim) and
1140
- * `renameTable` — where this instance is removed from the module's `tables` map
1141
- * and will never be used again. It best-effort flushes any buffered stats (the
1142
- * backing store is about to be deleted / relocated, so this is the last chance,
1143
- * same posture as the teardown-time `disconnect` it replaces), then runs the
1144
- * coordinator disposer so this instance's {stats apply/discard} callback pair —
1145
- * and the `this` its closures capture — is spliced off the shared coordinator's
1146
- * array rather than pinned for the module's lifetime.
1147
- *
1148
- * Idempotent: the disposer is run at most once and both it and the coordinator
1149
- * reference are nulled, so a double-dispose is a no-op and a re-`attachCoordinator`
1150
- * after dispose registers a fresh pair instead of double-registering.
1151
- */
1152
- dispose(): Promise<void>;
1153
- /** Get the current estimated row count. */
1154
- getEstimatedRowCount(): Promise<number>;
1155
- /** Track a mutation and schedule lazy stats persistence. */
1156
- protected trackMutation(delta: number, inTransaction?: boolean): void;
1157
- }
1158
- /** PK access pattern analysis result. */
1159
- interface PKAccessPattern {
1160
- type: 'point' | 'range' | 'scan';
1161
- values?: SqlValue[];
1162
- columnIndex?: number;
1163
- constraints?: Array<{
1164
- columnIndex: number;
1165
- op: IndexConstraintOp;
1166
- value?: SqlValue;
1167
- }>;
1168
- }
1169
- /**
1170
- * Secondary-index access pattern analysis result: the chosen index plus the
1171
- * encoded byte window {@link StoreTable.scanIndex} iterates. `point` is a
1172
- * leading-prefix EQ window, `range` a leading-column LT/LE/GT/GE window; both
1173
- * resolve to a `bounds` scan (an index seek is always a prefix scan, never a
1174
- * single entry).
1175
- */
1176
- interface IndexAccessPattern {
1177
- index: TableIndexSchema;
1178
- type: 'point' | 'range';
1179
- bounds: IterateOptions;
1180
208
  }
1181
- export {};
1182
209
  //# sourceMappingURL=store-table.d.ts.map