@quereus/store 4.5.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +34 -6
  2. package/dist/src/common/encoding.d.ts +4 -3
  3. package/dist/src/common/encoding.d.ts.map +1 -1
  4. package/dist/src/common/encoding.js +17 -13
  5. package/dist/src/common/encoding.js.map +1 -1
  6. package/dist/src/common/events.d.ts +31 -25
  7. package/dist/src/common/events.d.ts.map +1 -1
  8. package/dist/src/common/events.js +38 -39
  9. package/dist/src/common/events.js.map +1 -1
  10. package/dist/src/common/implicit-unique-index.d.ts +80 -0
  11. package/dist/src/common/implicit-unique-index.d.ts.map +1 -0
  12. package/dist/src/common/implicit-unique-index.js +148 -0
  13. package/dist/src/common/implicit-unique-index.js.map +1 -0
  14. package/dist/src/common/index.d.ts +7 -3
  15. package/dist/src/common/index.d.ts.map +1 -1
  16. package/dist/src/common/index.js +5 -2
  17. package/dist/src/common/index.js.map +1 -1
  18. package/dist/src/common/json-key.d.ts +30 -2
  19. package/dist/src/common/json-key.d.ts.map +1 -1
  20. package/dist/src/common/json-key.js +49 -2
  21. package/dist/src/common/json-key.js.map +1 -1
  22. package/dist/src/common/key-builder.d.ts +40 -20
  23. package/dist/src/common/key-builder.d.ts.map +1 -1
  24. package/dist/src/common/key-builder.js +31 -23
  25. package/dist/src/common/key-builder.js.map +1 -1
  26. package/dist/src/common/kv-store.d.ts +10 -0
  27. package/dist/src/common/kv-store.d.ts.map +1 -1
  28. package/dist/src/common/pk-key-resolution.d.ts +348 -0
  29. package/dist/src/common/pk-key-resolution.d.ts.map +1 -0
  30. package/dist/src/common/pk-key-resolution.js +481 -0
  31. package/dist/src/common/pk-key-resolution.js.map +1 -0
  32. package/dist/src/common/store-module-access-plan.d.ts +32 -0
  33. package/dist/src/common/store-module-access-plan.d.ts.map +1 -0
  34. package/dist/src/common/store-module-access-plan.js +428 -0
  35. package/dist/src/common/store-module-access-plan.js.map +1 -0
  36. package/dist/src/common/store-module-alter-column.d.ts +23 -0
  37. package/dist/src/common/store-module-alter-column.d.ts.map +1 -0
  38. package/dist/src/common/store-module-alter-column.js +389 -0
  39. package/dist/src/common/store-module-alter-column.js.map +1 -0
  40. package/dist/src/common/store-module-alter.d.ts +55 -0
  41. package/dist/src/common/store-module-alter.d.ts.map +1 -0
  42. package/dist/src/common/store-module-alter.js +504 -0
  43. package/dist/src/common/store-module-alter.js.map +1 -0
  44. package/dist/src/common/store-module-base.d.ts +268 -0
  45. package/dist/src/common/store-module-base.d.ts.map +1 -0
  46. package/dist/src/common/store-module-base.js +362 -0
  47. package/dist/src/common/store-module-base.js.map +1 -0
  48. package/dist/src/common/store-module-catalog.d.ts +269 -0
  49. package/dist/src/common/store-module-catalog.d.ts.map +1 -0
  50. package/dist/src/common/store-module-catalog.js +462 -0
  51. package/dist/src/common/store-module-catalog.js.map +1 -0
  52. package/dist/src/common/store-module-index-build.d.ts +88 -0
  53. package/dist/src/common/store-module-index-build.d.ts.map +1 -0
  54. package/dist/src/common/store-module-index-build.js +280 -0
  55. package/dist/src/common/store-module-index-build.js.map +1 -0
  56. package/dist/src/common/store-module-index.d.ts +116 -0
  57. package/dist/src/common/store-module-index.d.ts.map +1 -0
  58. package/dist/src/common/store-module-index.js +373 -0
  59. package/dist/src/common/store-module-index.js.map +1 -0
  60. package/dist/src/common/store-module-rename.d.ts +49 -0
  61. package/dist/src/common/store-module-rename.d.ts.map +1 -0
  62. package/dist/src/common/store-module-rename.js +268 -0
  63. package/dist/src/common/store-module-rename.js.map +1 -0
  64. package/dist/src/common/store-module-schema-rewrite.d.ts +57 -0
  65. package/dist/src/common/store-module-schema-rewrite.d.ts.map +1 -0
  66. package/dist/src/common/store-module-schema-rewrite.js +140 -0
  67. package/dist/src/common/store-module-schema-rewrite.js.map +1 -0
  68. package/dist/src/common/store-module-schema-sync.d.ts +232 -0
  69. package/dist/src/common/store-module-schema-sync.d.ts.map +1 -0
  70. package/dist/src/common/store-module-schema-sync.js +529 -0
  71. package/dist/src/common/store-module-schema-sync.js.map +1 -0
  72. package/dist/src/common/store-module.d.ts +43 -909
  73. package/dist/src/common/store-module.d.ts.map +1 -1
  74. package/dist/src/common/store-module.js +184 -3276
  75. package/dist/src/common/store-module.js.map +1 -1
  76. package/dist/src/common/store-table-base.d.ts +539 -0
  77. package/dist/src/common/store-table-base.d.ts.map +1 -0
  78. package/dist/src/common/store-table-base.js +900 -0
  79. package/dist/src/common/store-table-base.js.map +1 -0
  80. package/dist/src/common/store-table-constraints.d.ts +237 -0
  81. package/dist/src/common/store-table-constraints.d.ts.map +1 -0
  82. package/dist/src/common/store-table-constraints.js +624 -0
  83. package/dist/src/common/store-table-constraints.js.map +1 -0
  84. package/dist/src/common/store-table-scan.d.ts +452 -0
  85. package/dist/src/common/store-table-scan.d.ts.map +1 -0
  86. package/dist/src/common/store-table-scan.js +901 -0
  87. package/dist/src/common/store-table-scan.js.map +1 -0
  88. package/dist/src/common/store-table.d.ts +71 -1144
  89. package/dist/src/common/store-table.d.ts.map +1 -1
  90. package/dist/src/common/store-table.js +168 -2385
  91. package/dist/src/common/store-table.js.map +1 -1
  92. package/dist/src/testing/index.d.ts +12 -0
  93. package/dist/src/testing/index.d.ts.map +1 -0
  94. package/dist/src/testing/index.js +12 -0
  95. package/dist/src/testing/index.js.map +1 -0
  96. package/dist/src/testing/kv-assert.d.ts +19 -0
  97. package/dist/src/testing/kv-assert.d.ts.map +1 -0
  98. package/dist/src/testing/kv-assert.js +25 -0
  99. package/dist/src/testing/kv-assert.js.map +1 -0
  100. package/dist/src/testing/kv-conformance.d.ts.map +1 -1
  101. package/dist/src/testing/kv-conformance.js +1 -15
  102. package/dist/src/testing/kv-conformance.js.map +1 -1
  103. package/dist/src/testing/kv-provider-conformance.d.ts +54 -0
  104. package/dist/src/testing/kv-provider-conformance.d.ts.map +1 -0
  105. package/dist/src/testing/kv-provider-conformance.js +183 -0
  106. package/dist/src/testing/kv-provider-conformance.js.map +1 -0
  107. package/package.json +8 -8
@@ -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 IdxStrSpec, 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
11
  *
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.
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
164
17
  */
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
- *
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.
191
- */
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,635 +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, multi?: MultiSeekWindowContext): AsyncIterable<Row>;
758
- /**
759
- * Fail loudly on a multi-seek FilterInfo this table cannot serve. The plan already
760
- * dropped the residual Filter, and {@link matchesFilters} ANDs every pushed
761
- * constraint — so falling through to the scan arm would AND N mutually-exclusive
762
- * equalities and return zero rows: a silent wrong answer, not a slow one.
763
- */
764
- private multiSeekMalformed;
765
- /**
766
- * Decode a multi-seek FilterInfo into its N seek tuples of width W. Tuple i pairs
767
- * `args[i*W … i*W+W-1]` with the same slice of `constraints` (argvIndex runs
768
- * 1…N*W in order — see rule-select-access-path's multiSeek emission). A tuple with
769
- * a NULL/undefined component is dropped: IN is set membership and a NULL component
770
- * matches nothing. The planner pre-reduces literal lists, but a parameter-bound /
771
- * mixed-binding list reaches here unreduced, so this skip is the only line of
772
- * defense for those.
773
- */
774
- private decodeMultiSeekTuples;
775
- /**
776
- * Order one tuple's values to match `keyCols` (the leading index columns, or the
777
- * PK definition) using each constraint entry's column. The plan's seek columns and
778
- * the constraint list must agree; a tuple that does not cover exactly those
779
- * columns is malformed.
780
- */
781
- private orderTupleValues;
782
- /**
783
- * Serve a multi-seek (`plan=5`) FilterInfo: one point window per distinct
784
- * IN-list tuple, scanned in ascending encoded-key order.
785
- *
786
- * Window order is not cosmetic. Encoded-byte order IS index-key order (per-column
787
- * DESC inversion is baked into the bytes), and the isolation overlay merges an
788
- * index scan with its pending rows by (indexKey, PK) — an out-of-order underlying
789
- * stream misplaces overlay rows in the output. Two overlap hazards are folded
790
- * away before scanning:
791
- * - Tuples whose K-encoded prefix byte-matches (duplicate bound parameters, or
792
- * case variants under a NOCASE table key collation) share ONE window, each
793
- * kept as a residual alternative — see {@link MultiSeekWindowContext} for why
794
- * a single tuple's residual would drop rows when the comparison collation is
795
- * finer than K.
796
- * - A window with no finite upper bound (all-0xff prefix — see
797
- * buildIndexPrefixBounds) contains every later-sorting window outright (any
798
- * key ≥ an all-0xff prefix necessarily starts with it), so those windows fold
799
- * into it instead of re-scanning an overlapping range out of order.
800
- *
801
- * Emission is lazy per window — `… in (…) limit 1` stops after the first yielded
802
- * row without materializing the remaining windows. Each window goes through
803
- * {@link scanIndex} → {@link iterateEffective}, so read-your-own-writes and the
804
- * stale-entry / deleted-row defenses hold per seek key.
805
- */
806
- protected scanMultiSeek(spec: IdxStrSpec, filterInfo: FilterInfo): AsyncIterable<Row>;
807
- /**
808
- * Multi-seek over the PRIMARY key: each tuple is a full PK, resolved by a point
809
- * read in ascending encoded-data-key order (the table's native emission order).
810
- *
811
- * NOT reachable from this module's own plans today: `StoreModule.computeBestAccessPlan`
812
- * claims IN-list filters only for secondary indexes (see its EQ_OPS vs EQ_OR_IN_OPS
813
- * split and tickets/backlog/feat-store-pk-in-list-multiseek). The branch exists so
814
- * a `_primary_` multi-seek arriving from a future plan gets a correct answer rather
815
- * than the scan arm's silent zero rows, and it is what PK-IN enablement will build on.
816
- */
817
- protected scanMultiSeekPrimary(tuples: MultiSeekTuple[], seekWidth: number, filterInfo: FilterInfo): AsyncIterable<Row>;
818
- /**
819
- * Check if a row matches the filter constraints.
820
- *
821
- * `collations` maps a constrained column index to the comparison function that
822
- * column must be re-checked under; every caller builds it once per scan with
823
- * {@link resolveFilterCollations}, whose doc comment explains how the name is
824
- * chosen. A column absent from the map compares BINARY — the same result
825
- * {@link resolveFilterCollations} produces for an undeclared collation, and the
826
- * only reachable absence, since both walk the constraint list under identical
827
- * skip conditions.
828
- */
829
- protected matchesFilters(row: Row, filterInfo: FilterInfo, collations: ReadonlyMap<number, CollationFunction>): boolean;
830
- /**
831
- * Resolve, ONCE per scan, the comparison collation function for every pushed
832
- * constraint column, keyed by column index.
833
- *
834
- * The name for a column is its DECLARED collation (absent ⇒ BINARY) — the same
835
- * resolution the access path's collation-cover analysis uses
836
- * (indexColumnCollationLookup / primaryKeyCollationLookup) when it decides a
837
- * pushed constraint is fully covered, and the same source this file's UNIQUE
838
- * checks compare under. On a collation MATCH the planner drops the residual
839
- * Filter, so {@link matchesFilters} alone must reproduce the predicate.
840
- *
841
- * `indexCollationNames` (from {@link indexColumnCollations}) overrides the
842
- * declared name for the secondary-index scan arm: the planner's MATCH there is
843
- * against the INDEX column's collation, which an explicit `COLLATE` on the index
844
- * can make differ from the table column's.
845
- *
846
- * Names resolve against {@link collationResolver}, so an unregistered collation
847
- * raises `no such collation sequence` at scan setup rather than byte-ordering
848
- * every row.
849
- *
850
- * NOTE: rebuilt on every `query()` / `scanPKRange()` / `scanIndex()` call — one
851
- * registry lookup per distinct constrained column, dwarfed by the scan's I/O. If a
852
- * point-lookup-heavy profile ever shows it, memoize on the `FilterInfo`.
853
- */
854
- protected resolveFilterCollations(filterInfo: FilterInfo, indexCollationNames?: ReadonlyMap<number, string | undefined>): ReadonlyMap<number, CollationFunction>;
855
- /**
856
- * Effective per-column comparison collation for a secondary index's columns:
857
- * the index column's own `COLLATE` when present, else the underlying table
858
- * column's declared collation. Mirrors the resolution `StoreModule`'s
859
- * index-maintenance UNIQUE dedup uses (`indexCol.collation ?? tableColumn`), so
860
- * a re-checked index-scan row compares under the same collation the planner used
861
- * to justify dropping (or keeping) the residual Filter.
862
- */
863
- protected indexColumnCollations(index: TableIndexSchema): Map<number, string | undefined>;
864
- /**
865
- * Compare two values according to an operator, under `collationFunc` (already
866
- * resolved by {@link resolveFilterCollations} against this database's collation
867
- * registry). So the LT/LE/GT/GE range bounds honour a NOCASE/RTRIM/custom column
868
- * collation rather than a raw BINARY JS comparison — the capability
869
- * `StoreModule.getBestAccessPlan` advertises via `honorsCollatedRangeBounds`.
870
- * NULL on either side fails every operator except EQ-with-both-NULL (the
871
- * internal point-lookup convention; the planner never pushes `= NULL`).
872
- */
873
- protected compareValues(a: SqlValue, op: IndexConstraintOp, b: SqlValue, collationFunc: CollationFunction, semanticCompare?: (a: SqlValue, b: SqlValue) => number): boolean;
874
175
  /** Perform an update operation (INSERT, UPDATE, DELETE). */
875
176
  update(args: UpdateArgs): Promise<UpdateResult>;
876
- /**
877
- * Update secondary indexes after a row change.
878
- *
879
- * For PK-change UPDATE, `oldPk` (where the existing entry lives) and `newPk`
880
- * (where the relocated entry will live) differ; using a single pk for both
881
- * sides leaks the old entry. Other paths pass the same pk for both.
882
- */
883
- protected updateSecondaryIndexes(inTransaction: boolean, oldRow: Row | null, newRow: Row | null, oldPk: SqlValue[], newPk?: SqlValue[]): Promise<void>;
884
- /**
885
- * Returns the compiled predicate for a partial-UNIQUE constraint, or undefined
886
- * when the constraint covers the full table. Compilation is memoized per
887
- * UniqueConstraintSchema instance so the hot UNIQUE-check path doesn't recompile.
888
- */
889
- private compileFor;
890
- /**
891
- * Returns the compiled predicate for a partial secondary index, or undefined
892
- * for a full index. Compilation is memoized per IndexSchema instance so the hot
893
- * DML index-maintenance path doesn't recompile.
894
- */
895
- private compileIndexFor;
896
- /**
897
- * Check if two PK arrays name the same row. A semantic-ordering member compares
898
- * through its type's `compare` (see {@link resolvePkSemanticEquality}) so
899
- * differently-spelled equal values ('PT1H' / 'PT60M') answer "same row",
900
- * agreeing with the physical key identity {@link encodeDataKey} produces; every
901
- * other member keeps the original strict `!==`.
902
- */
903
- protected keysEqual(a: SqlValue[], b: SqlValue[]): boolean;
904
- /**
905
- * Per-constrained-column comparison functions for one UNIQUE constraint: the
906
- * type's `compare` for a semantic-ordering column (so 'PT1H' conflicts with
907
- * 'PT60M', matching the memory backend's typed BTree), else the enforcement
908
- * collation the caller resolved (`resolveUniqueEnforcementCollations`) through
909
- * `compareSqlValuesFast` — the exact comparison every finder used before.
910
- *
911
- * Thin wrapper over the engine's `uniqueEnforcementComparators`, which memory
912
- * and the isolation overlay share so the three backends cannot drift.
913
- */
914
- private uniqueColumnComparators;
915
- /**
916
- * Returns true if any column covered by a UNIQUE constraint differs between
917
- * oldRow and newRow, or — for partial UNIQUE — any column referenced by the
918
- * partial predicate differs (which can transition the row across the
919
- * predicate scope and re-trigger the uniqueness check).
920
- */
921
- protected uniqueColumnsChanged(oldRow: Row, newRow: Row): boolean;
922
- /**
923
- * Enforce table-level UNIQUE constraints against the prospective newRow.
924
- * Honors `onConflict`: IGNORE returns an ok-with-undefined-row; REPLACE
925
- * deletes the conflicting row(s) and continues; otherwise returns a
926
- * constraint result. Returns null when all constraints pass.
927
- *
928
- * Rows whose PK is in `selfPks` are skipped (the row being inserted/updated).
929
- * NULL in any covered column skips that constraint (multiple NULLs are allowed
930
- * per SQL standard).
931
- *
932
- * Reads through the transaction coordinator's pending writes when active so
933
- * intra-transaction duplicates are detected.
934
- *
935
- * REPLACE evictions (rows at OTHER PKs) are deleted from storage and pushed onto
936
- * `evicted` so the DML executor runs the full delete pipeline for each
937
- * (change-tracking, row-time MV maintenance, FK cascade, auto-events).
938
- */
939
- protected checkUniqueConstraints(inTransaction: boolean, newRow: Row, selfPks: SqlValue[][], onConflict: ConflictResolution | undefined, evicted: Row[]): Promise<UpdateResult | null>;
940
- /**
941
- * Route one UNIQUE constraint's conflict search to the cheapest SOUND finder,
942
- * in descending preference:
943
- *
944
- * 1. A linked row-time covering MV — its backing table (hosted by any
945
- * backing-host-capable module — memory by default, this store module under
946
- * `using store` — queried through the db with reads-own-writes) answers the
947
- * uniqueness question, mirroring the memory enforcement path.
948
- * 2. A physical secondary index realizing the constraint — one prefix seek
949
- * instead of a full table scan (see {@link findIndexForUniqueConstraint}).
950
- * 3. The full data-store scan ({@link findUniqueConflict}) — always correct,
951
- * O(rows) per checked row.
952
- *
953
- * Every finder returns the SAME `{pk, row}` shape, so the caller's conflict
954
- * action (ABORT / IGNORE / REPLACE eviction) is finder-independent.
955
- */
956
- private findUniqueConflictFor;
957
- /**
958
- * The `schema.indexes` entry whose physical index store can serve `uc`'s
959
- * conflict search as a point seek, or undefined when none can.
960
- *
961
- * Every non-derived UNIQUE now carries a materialized implicit index
962
- * (`_uc_*`, see {@link withImplicitUniqueIndexes}), so — like the memory
963
- * backend — a plain column- or table-level `UNIQUE` is index-servable, not
964
- * only an explicit `CREATE INDEX`. A UC is index-servable when:
965
- *
966
- * - it is index-derived (`derivedFromIndex`, from `CREATE UNIQUE INDEX`) and
967
- * its named index is still present — the index's partial predicate then
968
- * equals the constraint's by construction (`appendIndexToTableSchema`); or
969
- * - some index's columns equal `uc.columns` positionally AND whose predicate
970
- * is the SAME object as `uc.predicate` (reference identity — the implicit
971
- * materializer sets it that way). For a FULL UNIQUE that is
972
- * `undefined === undefined`, which also admits any user full index over the
973
- * same columns. For a PARTIAL `unique(...) where p` it admits only the
974
- * co-scoped `_uc_*` (which holds exactly the in-scope rows); an arbitrary
975
- * user partial index with a DIFFERENT predicate object stays conservative
976
- * (declined → full scan), because it physically omits rows the constraint
977
- * still covers. The serving index need not be UNIQUE — a plain index over
978
- * the constrained columns still holds every in-scope row.
979
- *
980
- * Collation guard (see {@link indexSeekHonorsEnforcementCollation}) may still
981
- * reject the found index, which routes the check back to the full scan.
982
- */
983
- private findIndexForUniqueConstraint;
984
- /**
985
- * True when a point seek into the index realizing `uc` returns a SUPERSET of
986
- * the constraint's true conflict set — the only condition under which the
987
- * seek may replace the full scan.
988
- *
989
- * An index key's leading (index-column) bytes are encoded under the TABLE KEY
990
- * collation K (`buildIndexKey` passes `this.encodeOptions`), NOT the index's
991
- * declared per-column COLLATE and NOT the constraint's enforcement collation
992
- * C (`uniqueEnforcementCollations` — the index's per-column COLLATE for an
993
- * index-derived UC, else the declared column collation). A seek therefore
994
- * fetches exactly `{rows K-equal to newRow}` while the re-validation keeps
995
- * `{rows C-equal to newRow}`. Soundness needs
996
- * `{C-equal} ⊆ {K-equal}`, i.e. K must be COARSER-OR-EQUAL to C per column:
997
- *
998
- * - non-text column → its bytes are type-native, collation-independent: safe.
999
- * - C == K → the sets coincide: safe.
1000
- * - K = NOCASE, C = BINARY → K strictly coarser: safe superset.
1001
- * - otherwise (K = BINARY over C = NOCASE/RTRIM; K = NOCASE over C = RTRIM)
1002
- * the seek UNDER-fetches and a real duplicate would be silently accepted:
1003
- * reject, so the caller full-scans.
1004
- *
1005
- * Same direction and same admitted cases as the read-side guard in
1006
- * `StoreModule.tryIndexAccessPlan`; conservative rather than exhaustive
1007
- * (K = RTRIM over C = BINARY is provably safe but declined), which costs an
1008
- * optimization, never correctness. The coarseness test is only sound for the
1009
- * built-in names: a custom K equals a custom C only when the index column names
1010
- * that same collation, and otherwise falls through to the full scan.
1011
- */
1012
- private indexSeekHonorsEnforcementCollation;
1013
- /**
1014
- * The index analogue of {@link findUniqueConflict}: seek the index realizing
1015
- * `uc` at the point formed by `newRow`'s constrained-column values, and
1016
- * re-validate each resolved candidate exactly as the full scan does.
1017
- *
1018
- * The seek encodes ALL of `uc.columns` (positionally aligned with the index's
1019
- * columns, guaranteed by `appendIndexToTableSchema` / the column-set match in
1020
- * {@link findIndexForUniqueConstraint}) as a leading prefix of the index key,
1021
- * under the same key collation and per-column DESC directions
1022
- * {@link updateSecondaryIndexes} used to write it. The remaining suffix is the
1023
- * row's PK, so the window spans every entry sharing those column values.
1024
- * {@link iterateEffective} merges this transaction's pending index puts/deletes
1025
- * over the committed entries, giving read-your-own-writes; each entry resolves
1026
- * to its LIVE row through the data key stored as the entry's value.
1027
- *
1028
- * The seek only narrows the CANDIDATE set to a superset (guaranteed by
1029
- * {@link indexSeekHonorsEnforcementCollation}); the authoritative comparison is
1030
- * the identical self-PK exclusion, per-column enforcement-collation compare,
1031
- * and partial-predicate scope check the full scan performs. A partial index
1032
- * already excludes out-of-scope rows physically — the predicate re-check is
1033
- * kept as defense in depth.
1034
- *
1035
- * Returns {@link INDEX_UNUSABLE} rather than a (possibly wrong) answer when an
1036
- * entry carries a legacy empty value.
1037
- */
1038
- private findUniqueConflictViaIndex;
1039
- /**
1040
- * Scan committed + pending data rows for a row matching `newRow` on
1041
- * `uc.columns` whose PK is not in `selfPks`. For partial UNIQUE, candidates
1042
- * whose row does not satisfy the predicate are skipped. Returns the first
1043
- * match or null.
1044
- */
1045
- private findUniqueConflict;
1046
- /**
1047
- * Find a UNIQUE conflict through a linked row-time covering MV's backing table
1048
- * (the store analogue of the memory `checkUniqueViaMaterializedView`). The
1049
- * backing scan yields candidate conflicting **source** PKs (reads-own-writes via
1050
- * the backing's coordinated connection); each is validated against the *live*
1051
- * store row (committed + this transaction's pending overlay) so a backing entry
1052
- * that lags a row deleted/updated internally this statement is skipped rather
1053
- * than raised as a false conflict. Returns the first real conflict or null.
1054
- */
1055
- private findUniqueConflictViaCoveringMv;
1056
- /**
1057
- * Read the live row at `pk` — this transaction's pending overlay (a pending
1058
- * delete ⇒ gone; a pending put ⇒ its value) shadowing the committed store.
1059
- * Backs `query()`'s point-lookup arm and validates covering-MV conflict
1060
- * candidates against the source of truth.
1061
- */
1062
- private readLiveRowByPk;
1063
- /** Encode `pkValues` (in PK-definition order) exactly as the data store keys rows. */
1064
- encodeDataKey(pkValues: SqlValue[]): Uint8Array;
1065
- /**
1066
- * Byte bounds covering every data key whose leading PK columns equal
1067
- * `prefixValues` — encoded under the same per-column DESC directions and key
1068
- * collations as {@link encodeDataKey}, so seek + early-terminate addresses the
1069
- * exact slice. An empty prefix yields full-scan bounds.
1070
- */
1071
- encodePkPrefixBounds(prefixValues: SqlValue[]): {
1072
- gte: Uint8Array;
1073
- lt?: Uint8Array;
1074
- };
1075
- /**
1076
- * Effective (pending-over-committed) point read by encoded data key: a pending
1077
- * delete ⇒ null, a pending put ⇒ its value, else the committed store entry.
1078
- */
1079
- readEffectiveRowByKey(key: Uint8Array): Promise<Row | null>;
1080
- /**
1081
- * Effective ordered entry scan within `bounds` (see {@link iterateEffective}).
1082
- * Opens the data store first — which fires the lazy first-access `saveTableDDL`
1083
- * for a freshly created table, the catalog write a store-backed MV backing
1084
- * relies on to survive reopen.
1085
- */
1086
- iterateEffectiveEntries(bounds: IterateOptions, reverse?: boolean): AsyncIterable<KVEntry>;
1087
- /** Buffer a privileged row-count delta, applied at coordinator commit (host writes). */
1088
- trackPrivilegedMutation(delta: number): void;
1089
- /**
1090
- * Declared secondary-UNIQUE enforcement for maintenance writes — the store
1091
- * mirror of the memory manager's `enforceSecondaryUniqueOnMaintenance` (see
1092
- * `vtab/backing-host.ts` § Constraint validation for the contract and
1093
- * docs/mv-constraints.md § Derived-row constraint validation for the
1094
- * semantics). Called by `StoreBackingHost.applyMaintenance` AFTER the op
1095
- * batch lands in the coordinator's pending state: post-batch is load-bearing
1096
- * (a `replace-all` diff applies puts before deletes, so a per-op check would
1097
- * false-positive when the derived set moves a unique value between primary
1098
- * keys), and checking only the WRITTEN images is complete (pre-existing
1099
- * contents already satisfied the constraint).
1100
- *
1101
- * Reuses {@link findUniqueConflict} — pending-overlay reads, per-column
1102
- * collations, NULL-pass, partial-predicate scope, self-PK exclusion — with
1103
- * the covering-MV route deliberately bypassed: a covering MV over THIS table
1104
- * is cascade-maintained only after the batch returns, so it lags the batch
1105
- * and would miss a same-batch colliding pair. The conflict action is a hard
1106
- * abort (a derivation write carries no user OR clause, and a declared
1107
- * `on conflict replace`/`ignore` default must not evict or drop derived
1108
- * rows). Per-image cost is one effective full scan: unlike the DML path
1109
- * ({@link findUniqueConflictFor}), this one is NOT routed through
1110
- * {@link findUniqueConflictViaIndex}, because a backing table keeps no
1111
- * secondary indexes by design — there is never an index store to seek.
1112
- *
1113
- * Zero overhead when the table declares no secondary UNIQUE (every MV-sugar
1114
- * backing, and most maintained tables): one empty-array check.
1115
- */
1116
- enforceSecondaryUniqueForMaintenance(changes: readonly BackingRowChange[]): Promise<void>;
1117
- /**
1118
- * Reset statistics to an absolute committed row count and flush immediately.
1119
- * Used by the host's `replaceContents` (create-fill / refresh), where the new
1120
- * count is exact, replacing any drifted delta-tracked estimate.
1121
- */
1122
- resetStats(rowCount: number): Promise<void>;
1123
- /**
1124
- * Open (and cache) the data store, firing the lazy first-access `saveTableDDL`.
1125
- * Public for the host's `replaceContents`, which writes the store directly.
1126
- */
1127
- openDataStore(): Promise<KVStore>;
1128
- /**
1129
- * Effective (pending-over-committed) point read by PK values — the public
1130
- * read an external writer issues before an upsert to learn the row's current
1131
- * image. Thin wrapper over the same private point-lookup that backs
1132
- * {@link query}'s point arm, so an external read merges pending-over-committed
1133
- * exactly like an engine read does.
1134
- */
1135
- readRowByPk(pk: SqlValue[]): Promise<Row | null>;
1136
177
  /**
1137
178
  * Apply externally-originated row ops directly to this source table's
1138
179
  * COMMITTED storage: table-owned data-key put/delete, secondary-index
@@ -1164,119 +205,5 @@ export declare class StoreTable extends VirtualTable {
1164
205
  * posture the prior raw-KV sync adapter took — not a regression, now stated.
1165
206
  */
1166
207
  applyExternalRowChanges(ops: readonly ExternalRowOp[]): Promise<BackingRowChange[]>;
1167
- /**
1168
- * Fully delete the row at `pk` (data + secondary indexes + stats + delete event).
1169
- * Used by REPLACE conflict resolution to evict a conflicting unique row before
1170
- * the caller's insert/update proceeds.
1171
- */
1172
- private deleteRowAt;
1173
- /**
1174
- * Begin a table-scoped transaction by ensuring the coordinator is active.
1175
- *
1176
- * Used by the isolation layer's flush path, which treats the underlying
1177
- * write as an independent mini-transaction. Idempotent: if the coordinator
1178
- * is already in a transaction (e.g. started by a registered connection),
1179
- * this is a no-op.
1180
- */
1181
- begin(): Promise<void>;
1182
- /**
1183
- * Commits a table-scoped transaction, flushing any buffered writes to the KV store.
1184
- * No-op if the coordinator is not currently in a transaction.
1185
- */
1186
- commit(): Promise<void>;
1187
- /**
1188
- * Rolls back a table-scoped transaction, discarding any buffered writes.
1189
- * No-op if the coordinator is not currently in a transaction.
1190
- */
1191
- rollback(): Promise<void>;
1192
- /** Disconnect from the store. */
1193
- disconnect(): Promise<void>;
1194
- /**
1195
- * Hard teardown: fully detach this instance from the module-wide coordinator.
1196
- *
1197
- * Distinct from the per-scan {@link disconnect} (which only flushes stats and
1198
- * deliberately keeps the table hooked mid-life). Called ONLY at the genuine
1199
- * eviction sites — `StoreModule.tearDownTableStorage` (drop / reclaim) and
1200
- * `renameTable` — where this instance is removed from the module's `tables` map
1201
- * and will never be used again. It best-effort flushes any buffered stats (the
1202
- * backing store is about to be deleted / relocated, so this is the last chance,
1203
- * same posture as the teardown-time `disconnect` it replaces), then runs the
1204
- * coordinator disposer so this instance's {stats apply/discard} callback pair —
1205
- * and the `this` its closures capture — is spliced off the shared coordinator's
1206
- * array rather than pinned for the module's lifetime.
1207
- *
1208
- * Idempotent: the disposer is run at most once and both it and the coordinator
1209
- * reference are nulled, so a double-dispose is a no-op and a re-`attachCoordinator`
1210
- * after dispose registers a fresh pair instead of double-registering.
1211
- */
1212
- dispose(): Promise<void>;
1213
- /** Get the current estimated row count. */
1214
- getEstimatedRowCount(): Promise<number>;
1215
- /** Track a mutation and schedule lazy stats persistence. */
1216
- protected trackMutation(delta: number, inTransaction?: boolean): void;
1217
- }
1218
- /** PK access pattern analysis result. */
1219
- interface PKAccessPattern {
1220
- type: 'point' | 'range' | 'scan';
1221
- values?: SqlValue[];
1222
- columnIndex?: number;
1223
- constraints?: Array<{
1224
- columnIndex: number;
1225
- op: IndexConstraintOp;
1226
- value?: SqlValue;
1227
- }>;
1228
- }
1229
- /**
1230
- * Secondary-index access pattern analysis result: the chosen index plus the
1231
- * encoded byte window {@link StoreTable.scanIndex} iterates. `point` is a
1232
- * leading-prefix EQ window, `range` a leading-column LT/LE/GT/GE window; both
1233
- * resolve to a `bounds` scan (an index seek is always a prefix scan, never a
1234
- * single entry).
1235
- */
1236
- interface IndexAccessPattern {
1237
- index: TableIndexSchema;
1238
- type: 'point' | 'range';
1239
- bounds: IterateOptions;
1240
- }
1241
- /** One pushed-constraint entry, as {@link FilterInfo.constraints} carries it. */
1242
- type ConstraintEntry = FilterInfo['constraints'][number];
1243
- /**
1244
- * One multi-seek tuple: its W constraint entries and their bound values, in
1245
- * constraint order (see {@link StoreTable.decodeMultiSeekTuples}).
1246
- */
1247
- interface MultiSeekTuple {
1248
- entries: ConstraintEntry[];
1249
- values: SqlValue[];
1250
- }
1251
- /**
1252
- * Per-window context a multi-seek passes to {@link StoreTable.scanIndex} (absent for
1253
- * ordinary single-window scans).
1254
- *
1255
- * `seen` — data-key hexes already YIELDED by an earlier window. Checked before the
1256
- * data-store read (a cross-window duplicate costs no extra `get`), added only on a
1257
- * yield: adding at visit time would let a stale index entry (row re-keyed since
1258
- * indexing) that FAILS its residual poison the set and suppress the row's live entry
1259
- * in a later window.
1260
- * NOTE: holds one hex string per row the multi-seek yields, for the whole seek — the
1261
- * only unbounded allocation on this path. Windows are byte-disjoint, so the only real
1262
- * duplicate source is a stale index entry; if a large-result `IN` ever shows up as a
1263
- * memory problem, the set can be scoped per window (or dropped for a consistent store).
1264
- *
1265
- * `collations` — the constraint collation map, resolved once per multi-seek (it is
1266
- * identical for every window).
1267
- *
1268
- * `extraTuples` — additional seek tuples whose K-encoded window byte-equals this
1269
- * window's (duplicate bound parameters, or case-variant values under a NOCASE table
1270
- * key collation K). A row is yielded when it matches the primary FilterInfo OR any of
1271
- * these: when the column's comparison collation C is strictly finer than K (K=NOCASE
1272
- * over a BINARY column — the one coarser pairing the plan admits), the merged tuples
1273
- * are C-distinct and each admits different rows, so a single tuple's residual would
1274
- * silently drop the other tuples' rows.
1275
- */
1276
- interface MultiSeekWindowContext {
1277
- seen: Set<string>;
1278
- collations: ReadonlyMap<number, CollationFunction>;
1279
- extraTuples: readonly FilterInfo[];
1280
208
  }
1281
- export {};
1282
209
  //# sourceMappingURL=store-table.d.ts.map