@quereus/store 4.5.0 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -6
- package/dist/src/common/encoding.d.ts +4 -3
- package/dist/src/common/encoding.d.ts.map +1 -1
- package/dist/src/common/encoding.js +17 -13
- package/dist/src/common/encoding.js.map +1 -1
- package/dist/src/common/events.d.ts +31 -25
- package/dist/src/common/events.d.ts.map +1 -1
- package/dist/src/common/events.js +38 -39
- package/dist/src/common/events.js.map +1 -1
- package/dist/src/common/implicit-unique-index.d.ts +80 -0
- package/dist/src/common/implicit-unique-index.d.ts.map +1 -0
- package/dist/src/common/implicit-unique-index.js +148 -0
- package/dist/src/common/implicit-unique-index.js.map +1 -0
- package/dist/src/common/index.d.ts +7 -3
- package/dist/src/common/index.d.ts.map +1 -1
- package/dist/src/common/index.js +5 -2
- package/dist/src/common/index.js.map +1 -1
- package/dist/src/common/json-key.d.ts +30 -2
- package/dist/src/common/json-key.d.ts.map +1 -1
- package/dist/src/common/json-key.js +49 -2
- package/dist/src/common/json-key.js.map +1 -1
- package/dist/src/common/key-builder.d.ts +40 -20
- package/dist/src/common/key-builder.d.ts.map +1 -1
- package/dist/src/common/key-builder.js +31 -23
- package/dist/src/common/key-builder.js.map +1 -1
- package/dist/src/common/kv-store.d.ts +10 -0
- package/dist/src/common/kv-store.d.ts.map +1 -1
- package/dist/src/common/pk-key-resolution.d.ts +348 -0
- package/dist/src/common/pk-key-resolution.d.ts.map +1 -0
- package/dist/src/common/pk-key-resolution.js +481 -0
- package/dist/src/common/pk-key-resolution.js.map +1 -0
- package/dist/src/common/store-module-access-plan.d.ts +32 -0
- package/dist/src/common/store-module-access-plan.d.ts.map +1 -0
- package/dist/src/common/store-module-access-plan.js +428 -0
- package/dist/src/common/store-module-access-plan.js.map +1 -0
- package/dist/src/common/store-module-alter-column.d.ts +23 -0
- package/dist/src/common/store-module-alter-column.d.ts.map +1 -0
- package/dist/src/common/store-module-alter-column.js +389 -0
- package/dist/src/common/store-module-alter-column.js.map +1 -0
- package/dist/src/common/store-module-alter.d.ts +55 -0
- package/dist/src/common/store-module-alter.d.ts.map +1 -0
- package/dist/src/common/store-module-alter.js +504 -0
- package/dist/src/common/store-module-alter.js.map +1 -0
- package/dist/src/common/store-module-base.d.ts +268 -0
- package/dist/src/common/store-module-base.d.ts.map +1 -0
- package/dist/src/common/store-module-base.js +362 -0
- package/dist/src/common/store-module-base.js.map +1 -0
- package/dist/src/common/store-module-catalog.d.ts +269 -0
- package/dist/src/common/store-module-catalog.d.ts.map +1 -0
- package/dist/src/common/store-module-catalog.js +462 -0
- package/dist/src/common/store-module-catalog.js.map +1 -0
- package/dist/src/common/store-module-index-build.d.ts +88 -0
- package/dist/src/common/store-module-index-build.d.ts.map +1 -0
- package/dist/src/common/store-module-index-build.js +280 -0
- package/dist/src/common/store-module-index-build.js.map +1 -0
- package/dist/src/common/store-module-index.d.ts +116 -0
- package/dist/src/common/store-module-index.d.ts.map +1 -0
- package/dist/src/common/store-module-index.js +373 -0
- package/dist/src/common/store-module-index.js.map +1 -0
- package/dist/src/common/store-module-rename.d.ts +49 -0
- package/dist/src/common/store-module-rename.d.ts.map +1 -0
- package/dist/src/common/store-module-rename.js +268 -0
- package/dist/src/common/store-module-rename.js.map +1 -0
- package/dist/src/common/store-module-schema-rewrite.d.ts +57 -0
- package/dist/src/common/store-module-schema-rewrite.d.ts.map +1 -0
- package/dist/src/common/store-module-schema-rewrite.js +140 -0
- package/dist/src/common/store-module-schema-rewrite.js.map +1 -0
- package/dist/src/common/store-module-schema-sync.d.ts +232 -0
- package/dist/src/common/store-module-schema-sync.d.ts.map +1 -0
- package/dist/src/common/store-module-schema-sync.js +529 -0
- package/dist/src/common/store-module-schema-sync.js.map +1 -0
- package/dist/src/common/store-module.d.ts +43 -909
- package/dist/src/common/store-module.d.ts.map +1 -1
- package/dist/src/common/store-module.js +184 -3276
- package/dist/src/common/store-module.js.map +1 -1
- package/dist/src/common/store-table-base.d.ts +539 -0
- package/dist/src/common/store-table-base.d.ts.map +1 -0
- package/dist/src/common/store-table-base.js +900 -0
- package/dist/src/common/store-table-base.js.map +1 -0
- package/dist/src/common/store-table-constraints.d.ts +237 -0
- package/dist/src/common/store-table-constraints.d.ts.map +1 -0
- package/dist/src/common/store-table-constraints.js +624 -0
- package/dist/src/common/store-table-constraints.js.map +1 -0
- package/dist/src/common/store-table-scan.d.ts +452 -0
- package/dist/src/common/store-table-scan.d.ts.map +1 -0
- package/dist/src/common/store-table-scan.js +901 -0
- package/dist/src/common/store-table-scan.js.map +1 -0
- package/dist/src/common/store-table.d.ts +71 -1144
- package/dist/src/common/store-table.d.ts.map +1 -1
- package/dist/src/common/store-table.js +168 -2385
- package/dist/src/common/store-table.js.map +1 -1
- package/dist/src/testing/index.d.ts +12 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +12 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/testing/kv-assert.d.ts +19 -0
- package/dist/src/testing/kv-assert.d.ts.map +1 -0
- package/dist/src/testing/kv-assert.js +25 -0
- package/dist/src/testing/kv-assert.js.map +1 -0
- package/dist/src/testing/kv-conformance.d.ts.map +1 -1
- package/dist/src/testing/kv-conformance.js +1 -15
- package/dist/src/testing/kv-conformance.js.map +1 -1
- package/dist/src/testing/kv-provider-conformance.d.ts +54 -0
- package/dist/src/testing/kv-provider-conformance.d.ts.map +1 -0
- package/dist/src/testing/kv-provider-conformance.js +183 -0
- package/dist/src/testing/kv-provider-conformance.js.map +1 -0
- package/package.json +8 -8
|
@@ -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
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|