@quereus/store 4.5.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +34 -6
  2. package/dist/src/common/encoding.d.ts +4 -3
  3. package/dist/src/common/encoding.d.ts.map +1 -1
  4. package/dist/src/common/encoding.js +17 -13
  5. package/dist/src/common/encoding.js.map +1 -1
  6. package/dist/src/common/events.d.ts +31 -25
  7. package/dist/src/common/events.d.ts.map +1 -1
  8. package/dist/src/common/events.js +38 -39
  9. package/dist/src/common/events.js.map +1 -1
  10. package/dist/src/common/implicit-unique-index.d.ts +80 -0
  11. package/dist/src/common/implicit-unique-index.d.ts.map +1 -0
  12. package/dist/src/common/implicit-unique-index.js +148 -0
  13. package/dist/src/common/implicit-unique-index.js.map +1 -0
  14. package/dist/src/common/index.d.ts +7 -3
  15. package/dist/src/common/index.d.ts.map +1 -1
  16. package/dist/src/common/index.js +5 -2
  17. package/dist/src/common/index.js.map +1 -1
  18. package/dist/src/common/json-key.d.ts +30 -2
  19. package/dist/src/common/json-key.d.ts.map +1 -1
  20. package/dist/src/common/json-key.js +49 -2
  21. package/dist/src/common/json-key.js.map +1 -1
  22. package/dist/src/common/key-builder.d.ts +40 -20
  23. package/dist/src/common/key-builder.d.ts.map +1 -1
  24. package/dist/src/common/key-builder.js +31 -23
  25. package/dist/src/common/key-builder.js.map +1 -1
  26. package/dist/src/common/kv-store.d.ts +10 -0
  27. package/dist/src/common/kv-store.d.ts.map +1 -1
  28. package/dist/src/common/pk-key-resolution.d.ts +348 -0
  29. package/dist/src/common/pk-key-resolution.d.ts.map +1 -0
  30. package/dist/src/common/pk-key-resolution.js +481 -0
  31. package/dist/src/common/pk-key-resolution.js.map +1 -0
  32. package/dist/src/common/store-module-access-plan.d.ts +32 -0
  33. package/dist/src/common/store-module-access-plan.d.ts.map +1 -0
  34. package/dist/src/common/store-module-access-plan.js +428 -0
  35. package/dist/src/common/store-module-access-plan.js.map +1 -0
  36. package/dist/src/common/store-module-alter-column.d.ts +23 -0
  37. package/dist/src/common/store-module-alter-column.d.ts.map +1 -0
  38. package/dist/src/common/store-module-alter-column.js +389 -0
  39. package/dist/src/common/store-module-alter-column.js.map +1 -0
  40. package/dist/src/common/store-module-alter.d.ts +55 -0
  41. package/dist/src/common/store-module-alter.d.ts.map +1 -0
  42. package/dist/src/common/store-module-alter.js +504 -0
  43. package/dist/src/common/store-module-alter.js.map +1 -0
  44. package/dist/src/common/store-module-base.d.ts +268 -0
  45. package/dist/src/common/store-module-base.d.ts.map +1 -0
  46. package/dist/src/common/store-module-base.js +362 -0
  47. package/dist/src/common/store-module-base.js.map +1 -0
  48. package/dist/src/common/store-module-catalog.d.ts +269 -0
  49. package/dist/src/common/store-module-catalog.d.ts.map +1 -0
  50. package/dist/src/common/store-module-catalog.js +462 -0
  51. package/dist/src/common/store-module-catalog.js.map +1 -0
  52. package/dist/src/common/store-module-index-build.d.ts +88 -0
  53. package/dist/src/common/store-module-index-build.d.ts.map +1 -0
  54. package/dist/src/common/store-module-index-build.js +280 -0
  55. package/dist/src/common/store-module-index-build.js.map +1 -0
  56. package/dist/src/common/store-module-index.d.ts +116 -0
  57. package/dist/src/common/store-module-index.d.ts.map +1 -0
  58. package/dist/src/common/store-module-index.js +373 -0
  59. package/dist/src/common/store-module-index.js.map +1 -0
  60. package/dist/src/common/store-module-rename.d.ts +49 -0
  61. package/dist/src/common/store-module-rename.d.ts.map +1 -0
  62. package/dist/src/common/store-module-rename.js +268 -0
  63. package/dist/src/common/store-module-rename.js.map +1 -0
  64. package/dist/src/common/store-module-schema-rewrite.d.ts +57 -0
  65. package/dist/src/common/store-module-schema-rewrite.d.ts.map +1 -0
  66. package/dist/src/common/store-module-schema-rewrite.js +140 -0
  67. package/dist/src/common/store-module-schema-rewrite.js.map +1 -0
  68. package/dist/src/common/store-module-schema-sync.d.ts +232 -0
  69. package/dist/src/common/store-module-schema-sync.d.ts.map +1 -0
  70. package/dist/src/common/store-module-schema-sync.js +529 -0
  71. package/dist/src/common/store-module-schema-sync.js.map +1 -0
  72. package/dist/src/common/store-module.d.ts +43 -909
  73. package/dist/src/common/store-module.d.ts.map +1 -1
  74. package/dist/src/common/store-module.js +184 -3276
  75. package/dist/src/common/store-module.js.map +1 -1
  76. package/dist/src/common/store-table-base.d.ts +539 -0
  77. package/dist/src/common/store-table-base.d.ts.map +1 -0
  78. package/dist/src/common/store-table-base.js +900 -0
  79. package/dist/src/common/store-table-base.js.map +1 -0
  80. package/dist/src/common/store-table-constraints.d.ts +237 -0
  81. package/dist/src/common/store-table-constraints.d.ts.map +1 -0
  82. package/dist/src/common/store-table-constraints.js +624 -0
  83. package/dist/src/common/store-table-constraints.js.map +1 -0
  84. package/dist/src/common/store-table-scan.d.ts +452 -0
  85. package/dist/src/common/store-table-scan.d.ts.map +1 -0
  86. package/dist/src/common/store-table-scan.js +901 -0
  87. package/dist/src/common/store-table-scan.js.map +1 -0
  88. package/dist/src/common/store-table.d.ts +71 -1144
  89. package/dist/src/common/store-table.d.ts.map +1 -1
  90. package/dist/src/common/store-table.js +168 -2385
  91. package/dist/src/common/store-table.js.map +1 -1
  92. package/dist/src/testing/index.d.ts +12 -0
  93. package/dist/src/testing/index.d.ts.map +1 -0
  94. package/dist/src/testing/index.js +12 -0
  95. package/dist/src/testing/index.js.map +1 -0
  96. package/dist/src/testing/kv-assert.d.ts +19 -0
  97. package/dist/src/testing/kv-assert.d.ts.map +1 -0
  98. package/dist/src/testing/kv-assert.js +25 -0
  99. package/dist/src/testing/kv-assert.js.map +1 -0
  100. package/dist/src/testing/kv-conformance.d.ts.map +1 -1
  101. package/dist/src/testing/kv-conformance.js +1 -15
  102. package/dist/src/testing/kv-conformance.js.map +1 -1
  103. package/dist/src/testing/kv-provider-conformance.d.ts +54 -0
  104. package/dist/src/testing/kv-provider-conformance.d.ts.map +1 -0
  105. package/dist/src/testing/kv-provider-conformance.js +183 -0
  106. package/dist/src/testing/kv-provider-conformance.js.map +1 -0
  107. package/package.json +8 -8
@@ -0,0 +1,452 @@
1
+ /**
2
+ * The read path of the KVStore-backed virtual table: turning a pushed predicate
3
+ * into a byte window over the data store or a secondary index, iterating that
4
+ * window, and re-checking every pushed constraint under the column's real
5
+ * collation.
6
+ *
7
+ * Second layer of the store-table chain:
8
+ * StoreTableBase -> StoreTableScan -> StoreTableConstraints -> StoreTable
9
+ */
10
+ import { IndexConstraintOp, type IdxStrSpec, type CollationFunction, type CompareFn, type LogicalType, type TableIndexSchema, type Row, type FilterInfo, type SqlValue } from '@quereus/quereus';
11
+ import type { IterateOptions, KVStore } from './kv-store.js';
12
+ import type { KeyValueTransform } from './encoding.js';
13
+ import { StoreTableBase } from './store-table-base.js';
14
+ /**
15
+ * Read-path layer of the generic KVStore-backed virtual table.
16
+ *
17
+ * Every arm below reads the EFFECTIVE row state (see
18
+ * {@link StoreTableBase.iterateEffective}), so a byte window narrowed here still
19
+ * honors read-your-own-writes.
20
+ */
21
+ export declare abstract class StoreTableScan extends StoreTableBase {
22
+ /**
23
+ * Query the table with optional filters.
24
+ *
25
+ * All three access arms read the EFFECTIVE row state: the committed store
26
+ * merged with this table's coordinator's pending ops when a transaction is
27
+ * active (read-your-own-writes — see {@link iterateEffective} /
28
+ * {@link readLiveRowByPk}). Merged emission stays in encoded-PK-key order,
29
+ * preserving the module's `providesOrdering` / `monotonicOn` advertisements.
30
+ */
31
+ query(filterInfo: FilterInfo): AsyncIterable<Row>;
32
+ /**
33
+ * True when a byte window over the LEADING PK column reproduces the comparator's order
34
+ * — the precondition {@link buildPKRangeBounds} needs, and the exact condition
35
+ * `computeBestAccessPlan` (store-module-access-plan.ts) uses before claiming the range filters handled.
36
+ * When false, the range arm degrades to a full scan + `matchesFilters` residual.
37
+ */
38
+ protected leadingPkRangeIsOrderSafe(): boolean;
39
+ /**
40
+ * True when some PK member's logical type carries semantic ordering (TIMESPAN,
41
+ * JSON) — the gate {@link scanMultiSeekPrimary} declines on.
42
+ *
43
+ * A primary-key multi-seek merges N byte windows with the residual Filter already
44
+ * dropped, and has no degradation available: a window that under-fetches loses rows
45
+ * outright, and falling back to the scan arm would AND N mutually-exclusive
46
+ * equalities to zero rows. So it fails loudly instead. Re-opening it is backlog
47
+ * `feat-store-semantic-key-multiseek`.
48
+ *
49
+ * NOT a gate on the single-value point arm any more: both types' key transforms (see
50
+ * {@link storeSemanticKeyTransform}) make byte equality exactly the type's equality
51
+ * ('PT1H' and 'PT60M' land on one key), so {@link analyzePKAccess} admits a point
52
+ * window per PROBE ({@link semanticProbeIsKeyFaithful}) rather than declining per
53
+ * schema. A single point window CAN degrade to the scan arm; N merged ones cannot.
54
+ */
55
+ protected pkHasSemanticOrderingMember(): boolean;
56
+ /** Analyze filter info to determine PK access pattern. */
57
+ protected analyzePKAccess(filterInfo: FilterInfo): PKAccessPattern;
58
+ /**
59
+ * Convert a leading-PK-column range access into one seek + early-terminate
60
+ * iterate window.
61
+ *
62
+ * Each LT/LE/GT/GE constraint's bound value is encoded under the SAME
63
+ * per-column DESC direction and key collation as the data keys (via
64
+ * {@link encodePkPrefixBounds}), giving the byte region `[lo, hi)` whose
65
+ * leading column equals that value (`lo = encode([x])`,
66
+ * `hi = incrementLastByte(lo)`). The op maps that region's endpoints onto a
67
+ * `gte`/`lt` window; because a DESC column bit-inverts its bytes (larger value
68
+ * ⇒ smaller bytes), the lower/upper assignment swaps with direction:
69
+ *
70
+ * | op | ASC | DESC |
71
+ * |----|----------|----------|
72
+ * | GE | gte = lo | lt = hi |
73
+ * | GT | gte = hi | lt = lo |
74
+ * | LE | lt = hi | gte = lo |
75
+ * | LT | lt = lo | gte = hi |
76
+ *
77
+ * Across constraints (BETWEEN ⇒ one lower + one upper; a redundant same-side
78
+ * pair ⇒ the tighter wins) we keep the MAX lower candidate for `gte` and the
79
+ * MIN upper candidate for `lt`. A candidate that resolves to `undefined` (an
80
+ * `hi` whose increment overflowed all-0xff) leaves that side unbounded — a safe
81
+ * SUPERSET, since {@link matchesFilters} stays the authoritative collation-aware
82
+ * row filter. A NULL/missing bound value is likewise skipped (the planner never
83
+ * pushes `= NULL`, and a range op against NULL rejects every row in matchesFilters).
84
+ * A bound over a semantic-ordering leading column whose VALUE has no faithful byte
85
+ * position ({@link semanticProbeIsKeyFaithful} — a numeric or unparseable TIMESPAN
86
+ * probe, a blob/bigint JSON probe) is skipped the same way: dropping a RANGE bound
87
+ * only WIDENS the window (worst case to the pre-existing full scan), and the plan
88
+ * claimed the range handled from the schema alone, so the predicate the engine's
89
+ * dropped Filter carried is reproduced by {@link matchesFilters} under the type's
90
+ * own compare. The EQUALITY arms cannot widen this way and degrade differently —
91
+ * see {@link semanticProbeIsKeyFaithful}'s per-arm table.
92
+ *
93
+ * NOTE: a range window over a text PK column is sound only when the column's key
94
+ * normalizer is ORDER-preserving with respect to its comparator — i.e. the comparator
95
+ * orders two strings the way memcmp orders their normalized bytes. That is NOT implied
96
+ * by `db.registerCollation`'s normalizer contract, which promises only an equality
97
+ * partition, and a built-in NAME may be re-registered with a comparator + normalizer
98
+ * pair that preserves equality while inverting order. This method is therefore only
99
+ * ever reached under {@link leadingPkRangeIsOrderSafe} — enforced by
100
+ * {@link analyzePKAccess} here and mirrored by `computeBestAccessPlan` (store-module-access-plan.ts)
101
+ * before it claims the range filters handled. A collation without the
102
+ * `orderPreserving` assertion costs the seek, never a row.
103
+ */
104
+ protected buildPKRangeBounds(access: PKAccessPattern): IterateOptions;
105
+ /**
106
+ * Scan a leading-PK-column range, seeking to the window start and
107
+ * early-terminating at its end.
108
+ *
109
+ * {@link buildPKRangeBounds} converts the LT/LE/GT/GE constraints into one
110
+ * encoded-byte `gte`/`lt` window under the same per-column DESC directions and
111
+ * key collations the data keys use, so the iterate visits a SUPERSET of the
112
+ * qualifying rows (a collation widening or the bound-byte increment can
113
+ * over-fetch, never under-fetch). {@link matchesFilters} stays the authoritative
114
+ * collation-aware row filter. {@link iterateEffective} restricts the pending
115
+ * merge to the same `bounds`, so read-your-own-writes holds on the narrowed window.
116
+ */
117
+ protected scanPKRange(store: KVStore, access: PKAccessPattern, filterInfo: FilterInfo): AsyncIterable<Row>;
118
+ /**
119
+ * Resolve the secondary index chosen by the planner from `filterInfo.idxStr`.
120
+ *
121
+ * The planner emits `idx=<name>(<n>);plan=…` when its access plan set both an
122
+ * `indexName` and `seekColumnIndexes` (see `getBestAccessPlan` and
123
+ * rule-select-access-path.ts). Decoding goes through the engine's shared
124
+ * `decodeIdxStr`, so the store, the in-memory vtab, and the isolation overlay all
125
+ * read one format. Returns null for the PK/scan sentinels (`_primary_`, `fullscan`,
126
+ * `empty`), an idxStr that names no index, or a name absent from `schema.indexes` —
127
+ * every one of which routes back to a PK/full-scan arm.
128
+ */
129
+ protected resolveIndexFromIdxStr(idxStr: string | null): TableIndexSchema | null;
130
+ /**
131
+ * Analyze filter info to determine a secondary-index access pattern, mirroring
132
+ * {@link analyzePKAccess} but over the index chosen in `idxStr`.
133
+ *
134
+ * A contiguous leading-prefix EQ on the index columns yields a `point` window
135
+ * (the prefix covers every entry sharing those leading values — an index seek
136
+ * is a PREFIX scan, not a single row, since the index need not be unique and
137
+ * the PK suffix varies); otherwise a range (LT/LE/GT/GE) on the LEADING index
138
+ * column yields a `range` window. Returns null when neither applies or when the
139
+ * index is unresolved. {@link matchesFilters} stays the authoritative row filter,
140
+ * so the window need only be a SUPERSET.
141
+ *
142
+ * Index-column bytes are encoded under each column's own key collation
143
+ * ({@link indexKeyCollations}); {@link matchesFilters} re-checks a fetched row under the
144
+ * index column's `COLLATE` else the table column's declared collation. The EQ/prefix
145
+ * window is EXACTLY the qualifying set when those two agree, which
146
+ * {@link indexPrefixSeekIsCollationExact} decides; the RANGE window additionally equates
147
+ * memcmp of the key bytes with the residual's comparison ORDER, which
148
+ * {@link indexRangeIsOrderSafe} decides. `tryIndexAccessPlan`
149
+ * (store-module-access-plan.ts) consults the SAME two predicates before claiming the
150
+ * filters handled, so the two decisions cannot disagree. Either failing returns null and
151
+ * the caller full-scans, where the residual is authoritative.
152
+ */
153
+ protected analyzeIndexAccess(filterInfo: FilterInfo): IndexAccessPattern | null;
154
+ /**
155
+ * Lazy cache behind {@link indexKeyCollations}: one resolved array per index-schema
156
+ * object, invalidated when the column array it was resolved against is replaced (an
157
+ * ALTER can retype a column without minting new index objects, so the index identity
158
+ * alone is not a safe key).
159
+ */
160
+ private readonly indexKeyCollationsCache;
161
+ /**
162
+ * Per-column KEY collation for `index`'s own columns, memoized — see
163
+ * {@link resolveIndexKeyCollations}. The one resolution every scan-side encode site
164
+ * ({@link analyzeIndexAccess}, {@link buildIndexRangeBounds}, {@link scanMultiSeek})
165
+ * threads into `buildIndexPrefixBounds`, so a scan window can never address
166
+ * different bytes than the maintenance writes.
167
+ */
168
+ protected indexKeyCollations(index: TableIndexSchema): (string | undefined)[];
169
+ /**
170
+ * Lazy cache behind {@link indexKeyTransforms} — the transform twin of
171
+ * {@link indexKeyCollationsCache}, with the same columns-identity invalidation rule
172
+ * (an ALTER can retype a column without minting new index objects).
173
+ */
174
+ private readonly indexKeyTransformsCache;
175
+ /**
176
+ * Per-column key VALUE TRANSFORM for `index`'s own columns, memoized — see
177
+ * {@link resolveIndexKeyTransforms}. Threaded, like {@link indexKeyCollations},
178
+ * into every scan-side `buildIndexPrefixBounds` call ({@link analyzeIndexAccess},
179
+ * {@link buildIndexRangeBounds}, {@link scanMultiSeek}) so a scan window addresses
180
+ * the same TRANSFORMED bytes `buildIndexKey` wrote for a semantic-ordering column.
181
+ * The point/multi-seek callers only ever see `undefined` entries today (their arms
182
+ * still decline semantic-ordering prefixes), but threading all three uniformly is
183
+ * the point — an arm re-opened later cannot silently address raw-value bytes.
184
+ */
185
+ protected indexKeyTransforms(index: TableIndexSchema): (KeyValueTransform | undefined)[];
186
+ /**
187
+ * Per-column comparators stating the store's actual index-key BYTE order for
188
+ * `indexName` — the `VirtualTable.getIndexComparator` isolation hook, mirroring
189
+ * `MemoryTable.getIndexComparator`. The isolation layer merges the overlay's pending
190
+ * rows against this table's index scan by `(indexKey, PK)` sort key and prefers these
191
+ * over its descriptor-derived fallback, so each column's comparator must reproduce
192
+ * the order {@link scanIndex} emits in:
193
+ * - the column's KEY collation ({@link indexKeyCollations}: index COLLATE, else the
194
+ * table column's declared collation, else BINARY) for text;
195
+ * - the logical type's `compare` for a semantic-ordering column (its key bytes
196
+ * encode through an order-preserving transform — see `storeSemanticKeyTransform`);
197
+ * - negated for a DESC column (its key bytes are bit-inverted).
198
+ * Resolved against the MATERIALIZED schema so a hidden `_uc_*` name resolves too.
199
+ *
200
+ * NOTE: for a text column this states the COMPARATOR's order, which is the byte order
201
+ * only while the collation's key normalizer preserves order (`_isCollationOrderPreserving`
202
+ * — asserted by the built-ins, opt-in for a custom one; see keyOrderMatchesCollation).
203
+ * Under an equality-only custom collation the emitted byte order and this comparator can
204
+ * disagree, misplacing an overlay row within an index-key-equal group. Same exposure the
205
+ * isolation layer's descriptor fallback carried before this method existed, so nothing
206
+ * regressed; if a custom equality-only collation on an INDEX column ever ships, gate this
207
+ * on the assertion (returning undefined declines to the fallback, which is no better —
208
+ * the real fix is to make the store emit in comparator order or the merge tolerate it).
209
+ */
210
+ getIndexComparator(indexName: string): CompareFn[] | undefined;
211
+ /**
212
+ * True when a byte window over the LEADING column of `index` reproduces the order the
213
+ * residual comparison uses. Delegates to {@link indexLeadingRangeIsOrderSafe} — the SAME
214
+ * call the range arm of `tryIndexAccessPlan` (store-module-access-plan.ts) makes before
215
+ * it marks the range filters handled, so the "build a window" and "mark handled"
216
+ * decisions cannot disagree. The memoized {@link indexKeyCollations} is passed as the
217
+ * key side, so the predicate judges the bytes this scan actually addresses.
218
+ */
219
+ protected indexRangeIsOrderSafe(index: TableIndexSchema): boolean;
220
+ /**
221
+ * Convert leading-index-column LT/LE/GT/GE constraints into one encoded-byte
222
+ * `gte`/`lt` window — the secondary-index analogue of {@link buildPKRangeBounds}.
223
+ *
224
+ * Each bound value is encoded under the leading index column's own key `collation`
225
+ * ({@link indexKeyCollations}) and DESC `dir` — exactly as {@link buildIndexKey}
226
+ * encodes that column — via {@link buildIndexPrefixBounds}, giving the byte region
227
+ * `[lo, hi)` whose leading column equals that value. The op maps that region's
228
+ * endpoints onto `gte`/`lt`, with the same DESC lower/upper SWAP as the PK path:
229
+ *
230
+ * | op | ASC | DESC |
231
+ * |----|----------|----------|
232
+ * | GE | gte = lo | lt = hi |
233
+ * | GT | gte = hi | lt = lo |
234
+ * | LE | lt = hi | gte = lo |
235
+ * | LT | lt = lo | gte = hi |
236
+ *
237
+ * Across constraints keep the MAX lower and MIN upper. An `undefined` upper (an
238
+ * `hi` whose increment overflowed all-0xff) leaves that side unbounded — a safe
239
+ * SUPERSET. A NULL/missing bound value is skipped (the planner never pushes
240
+ * `= NULL`, and a range op against NULL rejects every row in matchesFilters).
241
+ * A bound over a semantic-ordering leading column (`logicalType`) whose VALUE has
242
+ * no faithful byte position ({@link semanticProbeIsKeyFaithful}) is skipped the
243
+ * same way — a dropped RANGE bound only widens the window, and matchesFilters
244
+ * reproduces the predicate under the type's compare; see the widen-vs-decline
245
+ * note on {@link buildPKRangeBounds}. `transform` is the leading column's key
246
+ * value transform ({@link indexKeyTransforms}), so a surviving bound addresses
247
+ * the same transformed bytes the index store holds.
248
+ */
249
+ protected buildIndexRangeBounds(constraints: Array<{
250
+ op: IndexConstraintOp;
251
+ value?: SqlValue;
252
+ }>, dir: boolean, collation: string | undefined, logicalType: LogicalType | undefined, transform: KeyValueTransform | undefined): IterateOptions;
253
+ /**
254
+ * Scan a secondary index over `access.bounds`, resolving each index entry to its
255
+ * base row and re-filtering.
256
+ *
257
+ * {@link iterateEffective} yields the committed index entries merged with this
258
+ * transaction's pending index puts/deletes (read-your-own-writes over the
259
+ * index), in index-key byte order — the order the isolation overlay merge relies
260
+ * on (`isolated-table.ts` § buildSortKey). We resolve each entry to its row via
261
+ * its stored data-key value WITHOUT reordering, so index-key order is preserved.
262
+ *
263
+ * Defense in depth mirroring the memory layer's live-recheck: a resolved-null
264
+ * row (the entry's row was deleted — a pending index delete would normally
265
+ * suppress the entry, but a committed entry can lag) is skipped, and every
266
+ * resolved row is re-checked by {@link matchesFilters} (the byte window is only
267
+ * a superset, and a stale entry whose indexed column no longer matches is
268
+ * dropped).
269
+ */
270
+ protected scanIndex(indexStore: KVStore, access: IndexAccessPattern, filterInfo: FilterInfo, multi?: MultiSeekWindowContext): AsyncIterable<Row>;
271
+ /**
272
+ * Fail loudly on a multi-seek FilterInfo this table cannot serve. The plan already
273
+ * dropped the residual Filter, and {@link matchesFilters} ANDs every pushed
274
+ * constraint — so falling through to the scan arm would AND N mutually-exclusive
275
+ * equalities and return zero rows: a silent wrong answer, not a slow one.
276
+ */
277
+ private multiSeekMalformed;
278
+ /**
279
+ * Decode a multi-seek FilterInfo into its N seek tuples of width W. Tuple i pairs
280
+ * `args[i*W … i*W+W-1]` with the same slice of `constraints` (argvIndex runs
281
+ * 1…N*W in order — see rule-select-access-path's multiSeek emission). A tuple with
282
+ * a NULL/undefined component is dropped: IN is set membership and a NULL component
283
+ * matches nothing. The planner pre-reduces literal lists, but a parameter-bound /
284
+ * mixed-binding list reaches here unreduced, so this skip is the only line of
285
+ * defense for those.
286
+ */
287
+ private decodeMultiSeekTuples;
288
+ /**
289
+ * Order one tuple's values to match `keyCols` (the leading index columns, or the
290
+ * PK definition) using each constraint entry's column. The plan's seek columns and
291
+ * the constraint list must agree; a tuple that does not cover exactly those
292
+ * columns is malformed.
293
+ */
294
+ private orderTupleValues;
295
+ /**
296
+ * Serve a multi-seek (`plan=5`) FilterInfo: one point window per distinct
297
+ * IN-list tuple, scanned in ascending encoded-key order.
298
+ *
299
+ * Window order is not cosmetic. Encoded-byte order IS index-key order (per-column
300
+ * DESC inversion is baked into the bytes), and the isolation overlay merges an
301
+ * index scan with its pending rows by (indexKey, PK) — an out-of-order underlying
302
+ * stream misplaces overlay rows in the output. Two overlap hazards are folded
303
+ * away before scanning:
304
+ * - Tuples whose encoded prefix byte-matches (duplicate bound parameters, or
305
+ * case variants under a NOCASE key collation C) share ONE window, each kept
306
+ * as a residual alternative — see {@link MultiSeekWindowContext}.
307
+ * - A window with no finite upper bound (all-0xff prefix — see
308
+ * buildIndexPrefixBounds) contains every later-sorting window outright (any
309
+ * key ≥ an all-0xff prefix necessarily starts with it), so those windows fold
310
+ * into it instead of re-scanning an overlapping range out of order.
311
+ *
312
+ * Emission is lazy per window — `… in (…) limit 1` stops after the first yielded
313
+ * row without materializing the remaining windows. Each window goes through
314
+ * {@link scanIndex} → {@link iterateEffective}, so read-your-own-writes and the
315
+ * stale-entry / deleted-row defenses hold per seek key.
316
+ */
317
+ protected scanMultiSeek(spec: IdxStrSpec, filterInfo: FilterInfo): AsyncIterable<Row>;
318
+ /**
319
+ * Multi-seek over the PRIMARY key: each tuple is a full PK, resolved by a point
320
+ * read in ascending encoded-data-key order (the table's native emission order).
321
+ *
322
+ * NOT reachable from this module's own plans today: `computeBestAccessPlan` (store-module-access-plan.ts)
323
+ * claims IN-list filters only for secondary indexes (see its EQ_OPS vs EQ_OR_IN_OPS
324
+ * split and tickets/backlog/feat-store-pk-in-list-multiseek). The branch exists so
325
+ * a `_primary_` multi-seek arriving from a future plan gets a correct answer rather
326
+ * than the scan arm's silent zero rows, and it is what PK-IN enablement will build on.
327
+ */
328
+ protected scanMultiSeekPrimary(tuples: MultiSeekTuple[], seekWidth: number, filterInfo: FilterInfo): AsyncIterable<Row>;
329
+ /**
330
+ * Check if a row matches the filter constraints.
331
+ *
332
+ * `collations` maps a constrained column index to the comparison function that
333
+ * column must be re-checked under; every caller builds it once per scan with
334
+ * {@link resolveFilterCollations}, whose doc comment explains how the name is
335
+ * chosen. A column absent from the map compares BINARY — the same result
336
+ * {@link resolveFilterCollations} produces for an undeclared collation, and the
337
+ * only reachable absence, since both walk the constraint list under identical
338
+ * skip conditions.
339
+ */
340
+ protected matchesFilters(row: Row, filterInfo: FilterInfo, collations: ReadonlyMap<number, CollationFunction>): boolean;
341
+ /**
342
+ * Resolve, ONCE per scan, the comparison collation function for every pushed
343
+ * constraint column, keyed by column index.
344
+ *
345
+ * The name for a column is its DECLARED collation (absent ⇒ BINARY) — the same
346
+ * resolution the access path's collation-cover analysis uses
347
+ * (indexColumnCollationLookup / primaryKeyCollationLookup) when it decides a
348
+ * pushed constraint is fully covered, and the same source `StoreTableConstraints`'
349
+ * UNIQUE checks compare under. On a collation MATCH the planner drops the residual
350
+ * Filter, so {@link matchesFilters} alone must reproduce the predicate.
351
+ *
352
+ * `indexCollationNames` (from {@link indexColumnCollations}) overrides the
353
+ * declared name for the secondary-index scan arm: the planner's MATCH there is
354
+ * against the INDEX column's collation, which an explicit `COLLATE` on the index
355
+ * can make differ from the table column's.
356
+ *
357
+ * Names resolve against {@link collationResolver}, so an unregistered collation
358
+ * raises `no such collation sequence` at scan setup rather than byte-ordering
359
+ * every row.
360
+ *
361
+ * NOTE: rebuilt on every `query()` / `scanPKRange()` / `scanIndex()` call — one
362
+ * registry lookup per distinct constrained column, dwarfed by the scan's I/O. If a
363
+ * point-lookup-heavy profile ever shows it, memoize on the `FilterInfo`.
364
+ */
365
+ protected resolveFilterCollations(filterInfo: FilterInfo, indexCollationNames?: ReadonlyMap<number, string | undefined>): ReadonlyMap<number, CollationFunction>;
366
+ /**
367
+ * Effective per-column comparison collation for a secondary index's columns:
368
+ * the index column's own `COLLATE` when present, else the underlying table
369
+ * column's declared collation. Mirrors the resolution `StoreModule`'s
370
+ * index-maintenance UNIQUE dedup uses (`indexCol.collation ?? tableColumn`), so
371
+ * a re-checked index-scan row compares under the same collation the planner used
372
+ * to justify dropping (or keeping) the residual Filter.
373
+ */
374
+ protected indexColumnCollations(index: TableIndexSchema): Map<number, string | undefined>;
375
+ /**
376
+ * Compare two values according to an operator, under `collationFunc` (already
377
+ * resolved by {@link resolveFilterCollations} against this database's collation
378
+ * registry). So the LT/LE/GT/GE range bounds honour a NOCASE/RTRIM/custom column
379
+ * collation rather than a raw BINARY JS comparison — the capability
380
+ * `StoreModule.getBestAccessPlan` advertises via `honorsCollatedRangeBounds`.
381
+ * NULL on either side fails every operator except EQ-with-both-NULL (the
382
+ * internal point-lookup convention; the planner never pushes `= NULL`).
383
+ */
384
+ protected compareValues(a: SqlValue, op: IndexConstraintOp, b: SqlValue, collationFunc: CollationFunction, semanticCompare?: (a: SqlValue, b: SqlValue) => number): boolean;
385
+ }
386
+ /** PK access pattern analysis result. */
387
+ interface PKAccessPattern {
388
+ type: 'point' | 'range' | 'scan';
389
+ values?: SqlValue[];
390
+ columnIndex?: number;
391
+ constraints?: Array<{
392
+ columnIndex: number;
393
+ op: IndexConstraintOp;
394
+ value?: SqlValue;
395
+ }>;
396
+ }
397
+ /**
398
+ * Secondary-index access pattern analysis result: the chosen index plus the
399
+ * encoded byte window {@link StoreTableScan.scanIndex} iterates. `point` is a
400
+ * leading-prefix EQ window, `range` a leading-column LT/LE/GT/GE window; both
401
+ * resolve to a `bounds` scan (an index seek is always a prefix scan, never a
402
+ * single entry).
403
+ */
404
+ interface IndexAccessPattern {
405
+ index: TableIndexSchema;
406
+ type: 'point' | 'range';
407
+ bounds: IterateOptions;
408
+ }
409
+ /** One pushed-constraint entry, as {@link FilterInfo.constraints} carries it. */
410
+ type ConstraintEntry = FilterInfo['constraints'][number];
411
+ /**
412
+ * One multi-seek tuple: its W constraint entries and their bound values, in
413
+ * constraint order (see {@link StoreTableScan.decodeMultiSeekTuples}).
414
+ */
415
+ interface MultiSeekTuple {
416
+ entries: ConstraintEntry[];
417
+ values: SqlValue[];
418
+ }
419
+ /**
420
+ * Per-window context a multi-seek passes to {@link StoreTableScan.scanIndex} (absent for
421
+ * ordinary single-window scans).
422
+ *
423
+ * `seen` — data-key hexes already YIELDED by an earlier window. Checked before the
424
+ * data-store read (a cross-window duplicate costs no extra `get`), added only on a
425
+ * yield: adding at visit time would let a stale index entry (row re-keyed since
426
+ * indexing) that FAILS its residual poison the set and suppress the row's live entry
427
+ * in a later window.
428
+ * NOTE: holds one hex string per row the multi-seek yields, for the whole seek — the
429
+ * only unbounded allocation on this path. Windows are byte-disjoint, so the only real
430
+ * duplicate source is a stale index entry; if a large-result `IN` ever shows up as a
431
+ * memory problem, the set can be scoped per window (or dropped for a consistent store).
432
+ *
433
+ * `collations` — the constraint collation map, resolved once per multi-seek (it is
434
+ * identical for every window).
435
+ *
436
+ * `extraTuples` — additional seek tuples whose encoded window byte-equals this
437
+ * window's (duplicate bound parameters, or case-variant values under a NOCASE key
438
+ * collation). A row is yielded when it matches the primary FilterInfo OR any of
439
+ * these. Index bytes now encode under the same collation C the residual compares
440
+ * under (`indexKeyCollations`), so byte-equal tuples are C-equal and each merged
441
+ * tuple's residual admits the same rows — the OR is redundancy, kept because it is
442
+ * what makes the fold safe by construction rather than by that argument, and it is
443
+ * what a custom equality-only normalizer (byte-equal ⇏ comparator-equal order but
444
+ * equal partition) still relies on.
445
+ */
446
+ interface MultiSeekWindowContext {
447
+ seen: Set<string>;
448
+ collations: ReadonlyMap<number, CollationFunction>;
449
+ extraTuples: readonly FilterInfo[];
450
+ }
451
+ export {};
452
+ //# sourceMappingURL=store-table-scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-table-scan.d.ts","sourceRoot":"","sources":["../../../src/common/store-table-scan.ts"],"names":[],"mappings":"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"}