@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
|
@@ -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"}
|