@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,428 @@
1
+ /**
2
+ * Access planning for a store-backed table: which physical access path
3
+ * (`_primary_` point / range seek, a secondary-index seek or multi-seek, or a full
4
+ * scan) this module advertises for a pushed predicate, which of the pushed filters it
5
+ * claims as handled, and what ordering the chosen path provides.
6
+ *
7
+ * The mirror of `store-table-scan.ts`: the planner here decides which access path to
8
+ * advertise, and the scan layer there executes it. A plan that claims a filter the scan
9
+ * cannot honor drops the residual Filter and returns wrong rows, so every soundness
10
+ * predicate the two share lives in `pk-key-resolution.ts` (`keyOrderMatchesCollation`,
11
+ * `indexLeadingRangeIsOrderSafe`, `pkOrderPreservingPrefixLength`) rather than being
12
+ * restated here. The declines that are NOT shared — partial indexes, the multi-seek cap,
13
+ * and the semantic-ordering column ban on multi-seeks specifically — are stated in both
14
+ * files, and changing one means changing the other.
15
+ *
16
+ * Free functions rather than a layer of the store-module chain — access planning reads
17
+ * no module state beyond the table's configured key collation, which the caller passes in.
18
+ */
19
+ import { AccessPlanBuilder, equalitySeekKeyCount, hasSemanticOrdering, isMultiValueEquality } from '@quereus/quereus';
20
+ import { indexLeadingRangeIsOrderSafe, indexPrefixSeekIsCollationExact, pkOrderPreservingPrefixLength, resolveIndexKeyCollations, resolvePkKeyCollations, } from './pk-key-resolution.js';
21
+ /**
22
+ * Planner-side constraint operator groups, as `BestAccessPlanRequest.filters` spells
23
+ * them. Kept as one source of truth for the access-plan code below: {@link computeBestAccessPlan}
24
+ * classifies each pushed filter with these, and {@link tryIndexAccessPlan} claims a filter as
25
+ * handled only when it falls in the group the engine's access-path rule will consume.
26
+ */
27
+ const EQ_OPS = ['='];
28
+ /**
29
+ * Secondary-index arm ONLY: an IN is N equalities served as one multi-seek (`plan=5`),
30
+ * whether its members are a literal list or a runtime-valued set. The primary-key arms
31
+ * deliberately keep {@link EQ_OPS} — a `_primary_` multi-seek's emission order would
32
+ * break the isolation layer's primary-key merge — so an IN on the PK declines here,
33
+ * runtime-valued or not. PK IN support is deferred; see
34
+ * tickets/backlog/feat-store-pk-in-list-multiseek.
35
+ */
36
+ const EQ_OR_IN_OPS = ['=', 'IN'];
37
+ const LOWER_BOUND_OPS = ['>', '>='];
38
+ const UPPER_BOUND_OPS = ['<', '<='];
39
+ const RANGE_OPS = [...LOWER_BOUND_OPS, ...UPPER_BOUND_OPS];
40
+ /**
41
+ * Ceiling on the seek keys a single IN-list multi-seek may claim (the cross-product
42
+ * for a composite seek). Above it the plan declines to cost-only — residual retained,
43
+ * answer right, only the speed-up lost. The FK RESTRICT batch chunks at 500 keys, so
44
+ * 1000 leaves headroom while stopping an `a in (1..100) and b in (1..100)` 10k-seek
45
+ * explosion.
46
+ */
47
+ const MAX_MULTI_SEEK_KEYS = 1000;
48
+ /** Cost of positioning one index seek, in the same unit as the per-row fetch cost. */
49
+ const INDEX_SEEK_COST = 0.5;
50
+ /**
51
+ * Build `handledFilters` by claiming ONLY the constraints `rule-select-access-path`
52
+ * actually consumes: per seek column the FIRST '=' (equality seek), or the FIRST lower
53
+ * ('>'/'>=') plus the FIRST upper ('<'/'<=') bound, selected by `colConstraints.find(...)`
54
+ * in `request.filters` order.
55
+ *
56
+ * That rule collapses `handledFilters` into a per-COLUMN set, so a redundant same-column,
57
+ * same-role constraint marked handled is neither turned into a seek bound nor kept by
58
+ * `ruleGrowRetrieve` as a residual — its predicate would be LOST (`where v > 10 and v > 30`
59
+ * would wrongly return the `v > 10` rows; `where v = 20 and v = 30` would wrongly return
60
+ * the `v = 20` row). The engine now reattaches such orphans defensively, but a module that
61
+ * over-claims still pays a redundant predicate evaluation per fetched row.
62
+ *
63
+ * Claiming must therefore be POSITIONAL and match the rule's first-match pick: claiming the
64
+ * tighter-but-later duplicate instead would be actively wrong (the rule would still seek on
65
+ * the earlier one). Any later duplicate stays unhandled so it survives in the residual Filter.
66
+ */
67
+ function claimFirstPerRole(filters, roles) {
68
+ const claimed = new Set();
69
+ for (const { colIdx, ops } of roles) {
70
+ // An 'IN' fills an equality role only when well-formed — a non-empty literal list
71
+ // or a runtime-valued set. Same shape gate the rule's pick applies, so a malformed
72
+ // IN is neither claimed here nor seeked there and survives as a residual.
73
+ const i = filters.findIndex(f => f.columnIndex === colIdx && ops.includes(f.op)
74
+ && (f.op !== 'IN' || equalitySeekKeyCount(f) !== null));
75
+ if (i >= 0)
76
+ claimed.add(i);
77
+ }
78
+ return filters.map((_f, i) => claimed.has(i));
79
+ }
80
+ /** The lower-bound + upper-bound roles a single-column range seek on `colIdx` fills. */
81
+ function rangeRoles(colIdx) {
82
+ return [{ colIdx, ops: LOWER_BOUND_OPS }, { colIdx, ops: UPPER_BOUND_OPS }];
83
+ }
84
+ /**
85
+ * The one equality role each seek column of an equality/prefix seek fills. `ops` is
86
+ * explicit so the PK arm ({@link EQ_OPS}) and the secondary-index arm
87
+ * ({@link EQ_OR_IN_OPS}) cannot drift into each other — see the note on EQ_OR_IN_OPS.
88
+ */
89
+ function equalityRoles(colIdxs, ops) {
90
+ return colIdxs.map(colIdx => ({ colIdx, ops }));
91
+ }
92
+ /**
93
+ * The access plan this module advertises for `request`, before the caller stamps the
94
+ * module-wide `honorsCollatedRangeBounds` flag onto it.
95
+ *
96
+ * `tableKeyCollation` is the table's resolved key collation K, passed in rather than
97
+ * looked up: the caller (`StoreModule.getBestAccessPlan`) owns the module's table map.
98
+ * It is the PRIMARY-KEY arms' concern only — `resolvePkKeyCollations` still falls back to
99
+ * K for an undecorated text PK member. The secondary-index arm never sees it: index key
100
+ * bytes encode under each index column's own collation, so {@link tryIndexAccessPlan}
101
+ * judges its filters against that instead.
102
+ */
103
+ export function computeBestAccessPlan(db, tableInfo, request, tableKeyCollation) {
104
+ const estimatedRows = request.estimatedRows ?? 1000;
105
+ // Check for primary key equality constraints. Count DISTINCT pinned PK columns:
106
+ // counting raw '=' filters would read `a = 1 and a = 2` on a composite PK (a, b)
107
+ // as "both PK columns pinned", claim both filters handled, then — with no
108
+ // complete PK equality set to seek — degrade to a sequential scan whose residual
109
+ // has already been discarded, returning the whole table.
110
+ const pkColumns = tableInfo.primaryKeyDefinition.map(pk => pk.index);
111
+ const pinnedPkColumns = new Set(request.filters
112
+ .filter(f => f.columnIndex !== undefined && pkColumns.includes(f.columnIndex) && f.op === '=')
113
+ .map(f => f.columnIndex));
114
+ if (pinnedPkColumns.size === pkColumns.length && pkColumns.length > 0) {
115
+ // Full PK match - point lookup (single row; no monotonic advertisement)
116
+ return AccessPlanBuilder
117
+ .eqMatch(1, 0.1)
118
+ .setHandledFilters(claimFirstPerRole(request.filters, equalityRoles(pkColumns, EQ_OPS)))
119
+ .setIsSet(true)
120
+ .setIndexName('_primary_')
121
+ .setExplanation('Store primary key lookup')
122
+ .build();
123
+ }
124
+ // Check for range constraints on the leading PK column.
125
+ // The legacy access-path rule (rule-select-access-path.ts) only forwards
126
+ // range bounds for primaryKeyDefinition[0]; ranges on later PK columns
127
+ // are silently dropped if marked handled. So only claim handled=true
128
+ // when the range is on the first PK column.
129
+ //
130
+ // The seek is also declined when the leading PK column's key bytes do not order the
131
+ // way its comparator does (`pkOrderPreservingPrefix === 0`): `StoreTable.analyzePKAccess`
132
+ // declines the byte window under exactly that condition, so claiming the range filters
133
+ // handled here would drop the residual Filter and return the whole table.
134
+ const pkOrderPreservingPrefix = pkOrderPreservingPrefixLength(db, tableInfo, resolvePkKeyCollations(tableInfo.primaryKeyDefinition, tableInfo.columns, tableKeyCollation), tableKeyCollation);
135
+ const firstPkColumn = tableInfo.primaryKeyDefinition[0]?.index;
136
+ const hasLeadingPkRange = firstPkColumn !== undefined
137
+ && pkOrderPreservingPrefix >= 1
138
+ && request.filters.some(f => f.columnIndex === firstPkColumn && RANGE_OPS.includes(f.op));
139
+ if (hasLeadingPkRange) {
140
+ // Range scan on first PK column. Iteration is by PK key order (see
141
+ // StoreTable.scanPKRange), so we can advertise monotonic emission on
142
+ // the leading PK column. The scan seeks to the window start and
143
+ // early-terminates (StoreTable.buildPKRangeBounds derives the encoded
144
+ // bounds), and the leading-PK order guarantee holds throughout.
145
+ const rangeRows = Math.max(1, Math.floor(estimatedRows * 0.3));
146
+ const plan = AccessPlanBuilder
147
+ .rangeScan(rangeRows, 0.2)
148
+ .setHandledFilters(claimFirstPerRole(request.filters, rangeRoles(firstPkColumn)))
149
+ .setIndexName('_primary_')
150
+ .setSeekColumns([firstPkColumn])
151
+ .setExplanation('Store primary key range scan')
152
+ .build();
153
+ return { ...plan, ...buildPkOrderingAdvertisement(tableInfo, request, pkOrderPreservingPrefix) };
154
+ }
155
+ // Check for secondary index usage. `StoreTable.query` now implements the
156
+ // secondary-index scan arm (leading-prefix EQ point / leading-column range),
157
+ // so we advertise the index with `indexName` + `seekColumns` and mark the
158
+ // covered filters handled — subject to the range arm's order-safety gate in
159
+ // {@link tryIndexAccessPlan}. A cost-only plan (no seek) is kept as a fallback
160
+ // when no index yields a sound seek, preserving the prior "cheaper cost, filters
161
+ // unhandled, residual retained" behavior.
162
+ const indexes = tableInfo.indexes || [];
163
+ let bestSeekPlan = null;
164
+ let costOnlyFallback = null;
165
+ for (const index of indexes) {
166
+ if (index.columns.length === 0)
167
+ continue;
168
+ const plan = tryIndexAccessPlan(db, tableInfo, request, index, estimatedRows);
169
+ if (!plan)
170
+ continue;
171
+ // A fully-handled seek (indexName + seekColumns set) is a candidate: keep the
172
+ // cheapest one seen so far rather than the first, so declaration order of the
173
+ // indexes doesn't decide the plan. Strict '<' so ties keep the first candidate,
174
+ // matching MemoryTableModule.findBestAccessPlan's `indexPlan.cost < bestPlan.cost`.
175
+ if (plan.seekColumnIndexes && plan.seekColumnIndexes.length > 0) {
176
+ if (!bestSeekPlan || plan.cost < bestSeekPlan.cost)
177
+ bestSeekPlan = plan;
178
+ continue;
179
+ }
180
+ // Otherwise remember the first cost-only advertisement as a fallback.
181
+ if (!costOnlyFallback)
182
+ costOnlyFallback = plan;
183
+ }
184
+ if (bestSeekPlan)
185
+ return bestSeekPlan;
186
+ // NOTE: a cost-only plan carries no PK-order advertisement even though the store still
187
+ // iterates in PK key order for it (`StoreTable.query` full-scans), so `... where v > 'x'
188
+ // order by <pk>` picks up a Sort it did not need. Still true, just rarer than it was: an
189
+ // index range on a plain BINARY text column of a default-K (NOCASE) table now gets its
190
+ // seek and no longer lands here — what remains is a range under a collation without the
191
+ // `orderPreserving` assertion, an `any` column with a declared COLLATE, a semantic-ordering
192
+ // column, and the multi-seek declines. If it shows up as slow, merge
193
+ // `buildPkOrderingAdvertisement(...)` into this return.
194
+ //
195
+ // NOTE: cost-only fallback deliberately stays first-wins, not min-cost. These plans
196
+ // handle no filters — the scan full-scans regardless of which index "wins" — so
197
+ // "cheapest" among them isn't a meaningful ranking; picking a lower-cost one here would
198
+ // just under-state the plan's advertised cost to the optimizer without changing the work.
199
+ if (costOnlyFallback)
200
+ return costOnlyFallback;
201
+ // Fallback to full scan. The store iterates rows in PK key order
202
+ // (see StoreTable.query / store.iterate over buildFullScanBounds), so
203
+ // the scan is monotonic on the leading PK column. Advertise that so
204
+ // downstream rules (merge-join, asof-scan) can fire on store-backed
205
+ // tables, matching memory-mode behavior.
206
+ const plan = AccessPlanBuilder
207
+ .fullScan(estimatedRows)
208
+ .setHandledFilters(new Array(request.filters.length).fill(false))
209
+ .setExplanation('Store full table scan')
210
+ .build();
211
+ return { ...plan, ...buildPkOrderingAdvertisement(tableInfo, request, pkOrderPreservingPrefix) };
212
+ }
213
+ /**
214
+ * Build the access plan for one secondary index against `request`, or null when
215
+ * the index is not usable for this predicate.
216
+ *
217
+ * Usable = a contiguous leading-prefix EQ on the index columns (an index seek /
218
+ * point), or a LT/LE/GT/GE range on the LEADING index column. These mirror the
219
+ * two windows `StoreTable.analyzeIndexAccess` can build.
220
+ *
221
+ * **Collation safety.** Index-column key bytes are encoded under each column's own key
222
+ * collation (`resolveIndexKeyCollations`); `StoreTable.matchesFilters` re-checks a fetched
223
+ * row under the index column's `COLLATE` else the table column's declared collation. Both
224
+ * arms below ask whether those two agree — the question is no longer about the table key
225
+ * collation K at all, which is why this function never sees it:
226
+ *
227
+ * - EQUALITY — {@link indexPrefixSeekIsCollationExact}: agreement makes the window
228
+ * EXACTLY the qualifying set. A `text` or `any` column always agrees (both key under
229
+ * the same resolution the residual uses); a collation-blind column (`json`, the
230
+ * temporal types) under an index column with an explicit non-BINARY COLLATE does not
231
+ * (its key bytes are hard-BINARY) and declines.
232
+ * - RANGE — {@link indexLeadingRangeIsOrderSafe}: the same agreement PLUS the collation's
233
+ * `orderPreserving` assertion, because a byte window also equates memcmp of the key
234
+ * bytes with the residual comparator's order.
235
+ *
236
+ * `StoreTable.analyzeIndexAccess` gates its windows on the same two helpers, so the "mark
237
+ * handled" and "build a window" decisions cannot disagree. A decline returns a cost-only
238
+ * plan: cheaper cost, filters unhandled, residual retained; correct, just not sped up.
239
+ */
240
+ function tryIndexAccessPlan(db, tableInfo, request, index, estimatedRows) {
241
+ // Exclude PARTIAL indexes from access planning: neither the engine nor this
242
+ // module checks that the query's WHERE implies the index predicate, so seeking
243
+ // a partial index for a query it doesn't cover would silently drop the rows the
244
+ // index omits (an out-of-scope predicate returns nothing). Treat partial indexes
245
+ // purely as uniqueness enforcers — the query full-scans + residual instead.
246
+ // Mirrors MemoryTableModule.getAvailableIndexes (`if (idx.predicate) continue`).
247
+ if (index.predicate)
248
+ return null;
249
+ const indexColIndexes = index.columns.map(c => c.index);
250
+ // Contiguous leading-prefix equality → point/prefix seek. An IN filter counts as
251
+ // an equality here (it is N equalities, served as one multi-seek by
252
+ // StoreTable.scanMultiSeek); `inCount` is the cross-product of the per-column
253
+ // seek-key counts (1 for a plain '='), matching the rule's seek-key count. The
254
+ // FIRST role-filling filter per column is what the rule seeks on, so its
255
+ // cardinality is the one that counts.
256
+ //
257
+ // A runtime-valued IN set contributes its `maxCount` ceiling — the worst case the
258
+ // engine may deliver — so every gate below (the MAX_MULTI_SEEK_KEYS cap, the
259
+ // semantic-ordering decline) judges it against the largest multi-seek it could
260
+ // ever be asked to perform.
261
+ const eqCols = [];
262
+ let inCount = 1;
263
+ // `isMultiSeek` is NOT `inCount > 1`: a runtime set is delivered as a `plan=5`
264
+ // multi-seek even at `maxCount === 1`, so the gates below must judge it as one
265
+ // rather than as the plain EQ its ceiling arithmetic would otherwise suggest.
266
+ // (`inCount > 1` implies this flag — every factor above 1 is a multi-value
267
+ // equality — so the flag only ever *adds* the maxCount-1 runtime-set case.)
268
+ let isMultiSeek = false;
269
+ for (const colIdx of indexColIndexes) {
270
+ const eqFilter = request.filters.find(f => f.columnIndex === colIdx && equalitySeekKeyCount(f) !== null);
271
+ if (!eqFilter)
272
+ break;
273
+ eqCols.push(colIdx);
274
+ inCount *= equalitySeekKeyCount(eqFilter);
275
+ if (isMultiValueEquality(eqFilter))
276
+ isMultiSeek = true;
277
+ }
278
+ const leadingCol = indexColIndexes[0];
279
+ const hasLeadingRange = request.filters.some(f => f.columnIndex === leadingCol && RANGE_OPS.includes(f.op));
280
+ let seekCols;
281
+ let isRange;
282
+ if (eqCols.length > 0) {
283
+ seekCols = eqCols;
284
+ isRange = false;
285
+ }
286
+ else if (hasLeadingRange) {
287
+ seekCols = [leadingCol];
288
+ isRange = true;
289
+ }
290
+ else {
291
+ return null; // this index cannot serve this predicate
292
+ }
293
+ const rows = isRange
294
+ ? Math.max(1, Math.floor(estimatedRows * 0.3))
295
+ : Math.max(1, Math.floor(estimatedRows * 0.1));
296
+ const costOnly = (why) => (isRange ? AccessPlanBuilder.rangeScan(rows, 0.2) : AccessPlanBuilder.eqMatch(rows, 0.3))
297
+ .setHandledFilters(new Array(request.filters.length).fill(false))
298
+ .setExplanation(`Store index scan on ${index.name} (${why})`)
299
+ .build();
300
+ // Collation gates — see the doc comment above. `StoreTable.analyzeIndexAccess` declines
301
+ // exactly the same windows through the same two helpers.
302
+ const indexKeyCollations = resolveIndexKeyCollations(index, tableInfo.columns);
303
+ if (isRange) {
304
+ if (!indexLeadingRangeIsOrderSafe(db, tableInfo.columns, index, indexKeyCollations)) {
305
+ return costOnly('cost-only; index range needs an order-preserving key collation');
306
+ }
307
+ }
308
+ else if (!indexPrefixSeekIsCollationExact(tableInfo.columns, index, indexKeyCollations, seekCols.length)) {
309
+ return costOnly('cost-only; index key collation differs from the comparison collation');
310
+ }
311
+ // Multi-seek declines. Cost-only keeps the residual, so the answer stays right
312
+ // and only the speed-up is lost.
313
+ if (inCount > MAX_MULTI_SEEK_KEYS) {
314
+ return costOnly(`cost-only; IN cross-product of ${inCount} exceeds the ${MAX_MULTI_SEEK_KEYS}-seek cap`);
315
+ }
316
+ if (isMultiSeek && seekCols.some(colIdx => hasSemanticOrdering(tableInfo.columns[colIdx]?.logicalType))) {
317
+ // A plain EQ on a TIMESPAN/JSON column now seeks: `StoreTable.analyzeIndexAccess`
318
+ // encodes the probe through the column's key transform ('PT1H' and 'PT60M' collide
319
+ // on one key) and, for a probe with no faithful byte position, simply stops its
320
+ // prefix short and lets the residual re-filter under the type's compare. A
321
+ // multi-seek has neither escape — its merged windows ARE the whole access, with the
322
+ // residual already dropped — so an unfaithful member of the IN list would silently
323
+ // lose its tuple's rows or raise out of the key encoder. See `scanMultiSeek` and
324
+ // backlog `feat-store-semantic-key-multiseek`.
325
+ return costOnly('cost-only; semantic-ordering seek column cannot multi-seek');
326
+ }
327
+ // Claim positionally — see {@link claimFirstPerRole}.
328
+ const handledFilters = claimFirstPerRole(request.filters, isRange ? rangeRoles(leadingCol) : equalityRoles(eqCols, EQ_OR_IN_OPS));
329
+ if (isMultiSeek) {
330
+ // Multi-seek (plan=5): inCount point seeks, `rows` matched per seek key. The
331
+ // per-seek positioning term keeps a 500-key IN over a 10-row table from pricing
332
+ // below a full scan and issuing 500 seeks to read 10 rows. `isSet` false mirrors
333
+ // MemoryTableModule.evaluateIndexAccess's setIsSet(!isMultiSeek). No ordering is
334
+ // advertised — window emission order is encoded-key order, not any column order.
335
+ const multiRows = Math.min(estimatedRows, inCount * rows);
336
+ return AccessPlanBuilder.eqMatch(multiRows, inCount * INDEX_SEEK_COST)
337
+ .setIsSet(false)
338
+ .setHandledFilters(handledFilters)
339
+ .setIndexName(index.name)
340
+ .setSeekColumns(seekCols)
341
+ .setExplanation(`Store index multi-seek(${inCount}) on ${index.name}`)
342
+ .build();
343
+ }
344
+ return (isRange ? AccessPlanBuilder.rangeScan(rows, 0.2) : AccessPlanBuilder.eqMatch(rows, 0.3))
345
+ .setHandledFilters(handledFilters)
346
+ .setIndexName(index.name)
347
+ .setSeekColumns(seekCols)
348
+ .setExplanation(`Store index ${isRange ? 'range scan' : 'seek'} on ${index.name}`)
349
+ .build();
350
+ }
351
+ /**
352
+ * Compute the PK-ordering advertisement for a scan-style plan. Returns the
353
+ * `providesOrdering` / `monotonicOn` / `supportsAsofRight` fields for a plan
354
+ * whose iteration is driven by the primary-key key order (full scan or PK
355
+ * range scan).
356
+ *
357
+ * `providesOrdering` is set only when it actually matches what the caller
358
+ * needs:
359
+ * - When the request carries `requiredOrdering`, claim it only if the
360
+ * requested keys form a prefix of the PK with matching directions.
361
+ * Claiming PK order against an `ORDER BY <other column>` would cause
362
+ * the absorb-Sort rule to drop the Sort and yield wrong-order rows.
363
+ * - When no `requiredOrdering` is present, advertise the full PK
364
+ * ordering so downstream rules (merge-join, sort elision after a
365
+ * filter) can opportunistically use it.
366
+ *
367
+ * `monotonicOn` reflects the access path itself and is independent of any
368
+ * `requiredOrdering`; it always advertises the leading PK column. Strict
369
+ * monotonicity is claimed iff the PK is single-column — composite PKs can
370
+ * repeat values on the leading column.
371
+ *
372
+ * Returns an empty object when there is no PK (heap-only table) — without a
373
+ * leading key column there is no natural emit order.
374
+ *
375
+ * Every claim here is about the PHYSICAL key-byte order the store iterates in, but the
376
+ * consumers of `providesOrdering` / `monotonicOn` reason in the columns' COLLATION order.
377
+ * `orderPreservingPrefix` (from {@link pkOrderPreservingPrefixLength}) is how many leading
378
+ * PK members those two orders provably agree on: the advertisement is truncated to that
379
+ * prefix, and voided entirely when even the leading member disagrees — otherwise the
380
+ * absorb-Sort rule would elide a Sort and hand the caller byte-ordered rows.
381
+ */
382
+ function buildPkOrderingAdvertisement(tableInfo, request, orderPreservingPrefix) {
383
+ const pk = tableInfo.primaryKeyDefinition;
384
+ if (pk.length === 0 || orderPreservingPrefix === 0)
385
+ return {};
386
+ const leading = pk[0];
387
+ const monotonicOn = {
388
+ columnIndex: leading.index,
389
+ direction: leading.desc ? 'desc' : 'asc',
390
+ strict: pk.length === 1,
391
+ };
392
+ const pkOrdering = pk.slice(0, orderPreservingPrefix).map(col => ({
393
+ columnIndex: col.index,
394
+ desc: !!col.desc,
395
+ }));
396
+ // Pick the providesOrdering to advertise based on requiredOrdering.
397
+ const required = request.requiredOrdering;
398
+ let providesOrdering;
399
+ if (required && required.length > 0) {
400
+ // Only claim ordering when the requested keys form a prefix of the
401
+ // PK with matching directions. nullsFirst is intentionally not
402
+ // matched here — if the request specifies an explicit NULLS
403
+ // FIRST/LAST, leave the Sort in place rather than assume the PK
404
+ // scan's natural NULL placement matches.
405
+ if (required.length > pkOrdering.length)
406
+ return { monotonicOn, supportsAsofRight: true };
407
+ for (let i = 0; i < required.length; i++) {
408
+ if (required[i].columnIndex !== pkOrdering[i].columnIndex)
409
+ return { monotonicOn, supportsAsofRight: true };
410
+ if (required[i].desc !== pkOrdering[i].desc)
411
+ return { monotonicOn, supportsAsofRight: true };
412
+ if (required[i].nullsFirst !== undefined)
413
+ return { monotonicOn, supportsAsofRight: true };
414
+ }
415
+ providesOrdering = required;
416
+ }
417
+ else {
418
+ providesOrdering = pkOrdering;
419
+ }
420
+ return {
421
+ providesOrdering,
422
+ orderingIndexName: '_primary_',
423
+ monotonicOn,
424
+ supportsAsofRight: true,
425
+ };
426
+ }
427
+ // --- StoreTableModule interface implementation ---
428
+ //# sourceMappingURL=store-module-access-plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-module-access-plan.js","sourceRoot":"","sources":["../../../src/common/store-module-access-plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * ALTER TABLE ... ALTER COLUMN: the one ALTER arm that can rewrite stored values and
3
+ * re-encode physical keys. The orchestrating method decides the order of the throw-only
4
+ * checks and the mutations; the per-attribute sub-branches below it are pure — each
5
+ * returns the rewritten column schema plus a DEFERRED value-rewrite closure, mutating
6
+ * nothing itself.
7
+ *
8
+ * Fifth layer of the store-module chain:
9
+ * StoreModuleBase -> StoreModuleCatalog -> StoreModuleSchemaSync -> StoreModuleIndex
10
+ * -> StoreModuleAlterColumn -> StoreModuleAlter -> StoreModuleRename -> StoreModule
11
+ */
12
+ import type { Database, EffectiveRowSource, SchemaChangeInfo, TableSchema } from '@quereus/quereus';
13
+ import { StoreTable } from './store-table.js';
14
+ import { StoreModuleIndex } from './store-module-index.js';
15
+ export declare abstract class StoreModuleAlterColumn extends StoreModuleIndex {
16
+ /** ALTER COLUMN arm of `StoreModuleAlter.alterTable`: change one attribute (NOT NULL / data type /
17
+ * default / collation), running the physical re-key + existing-row re-validation the
18
+ * collation / PK paths require, then persist. Behavior-preserving extraction. */
19
+ protected alterColumnChange(db: Database, schemaName: string, tableName: string, table: StoreTable, oldSchema: TableSchema, change: Extract<SchemaChangeInfo, {
20
+ type: 'alterColumn';
21
+ }>, rows?: EffectiveRowSource): Promise<TableSchema>;
22
+ }
23
+ //# sourceMappingURL=store-module-alter-column.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-module-alter-column.d.ts","sourceRoot":"","sources":["../../../src/common/store-module-alter-column.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAEX,QAAQ,EACR,kBAAkB,EAElB,gBAAgB,EAEhB,WAAW,EACX,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAyB3D,8BAAsB,sBAAuB,SAAQ,gBAAgB;IACpE;;sFAEkF;cAClE,iBAAiB,CAChC,EAAE,EAAE,QAAQ,EACZ,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,WAAW,EACtB,MAAM,EAAE,OAAO,CAAC,gBAAgB,EAAE;QAAE,IAAI,EAAE,aAAa,CAAA;KAAE,CAAC,EAC1D,IAAI,CAAC,EAAE,kBAAkB,GACvB,OAAO,CAAC,WAAW,CAAC;CAwNvB"}