@quereus/quereus 4.14.0 → 4.16.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 (84) hide show
  1. package/dist/src/core/statement.d.ts +30 -0
  2. package/dist/src/core/statement.d.ts.map +1 -1
  3. package/dist/src/core/statement.js +49 -0
  4. package/dist/src/core/statement.js.map +1 -1
  5. package/dist/src/emit/ast-stringify.d.ts.map +1 -1
  6. package/dist/src/emit/ast-stringify.js +0 -4
  7. package/dist/src/emit/ast-stringify.js.map +1 -1
  8. package/dist/src/func/builtins/explain.d.ts.map +1 -1
  9. package/dist/src/func/builtins/explain.js +88 -61
  10. package/dist/src/func/builtins/explain.js.map +1 -1
  11. package/dist/src/func/registration.d.ts +7 -0
  12. package/dist/src/func/registration.d.ts.map +1 -1
  13. package/dist/src/func/registration.js +7 -0
  14. package/dist/src/func/registration.js.map +1 -1
  15. package/dist/src/index.d.ts +4 -1
  16. package/dist/src/index.d.ts.map +1 -1
  17. package/dist/src/index.js +2 -1
  18. package/dist/src/index.js.map +1 -1
  19. package/dist/src/parser/ast.d.ts +0 -2
  20. package/dist/src/parser/ast.d.ts.map +1 -1
  21. package/dist/src/parser/parser.d.ts +1 -0
  22. package/dist/src/parser/parser.d.ts.map +1 -1
  23. package/dist/src/parser/parser.js +29 -28
  24. package/dist/src/parser/parser.js.map +1 -1
  25. package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
  26. package/dist/src/planner/nodes/table-access-nodes.js +31 -7
  27. package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
  28. package/dist/src/planner/stats/analyze.d.ts.map +1 -1
  29. package/dist/src/planner/stats/analyze.js +9 -3
  30. package/dist/src/planner/stats/analyze.js.map +1 -1
  31. package/dist/src/runtime/emit/analyze.d.ts.map +1 -1
  32. package/dist/src/runtime/emit/analyze.js +1 -2
  33. package/dist/src/runtime/emit/analyze.js.map +1 -1
  34. package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
  35. package/dist/src/runtime/emit/dml-executor.js +23 -0
  36. package/dist/src/runtime/emit/dml-executor.js.map +1 -1
  37. package/dist/src/runtime/emit/scan.d.ts.map +1 -1
  38. package/dist/src/runtime/emit/scan.js +52 -9
  39. package/dist/src/runtime/emit/scan.js.map +1 -1
  40. package/dist/src/runtime/emit/set-object-tags.js +4 -4
  41. package/dist/src/runtime/emit/set-object-tags.js.map +1 -1
  42. package/dist/src/runtime/emitters.d.ts.map +1 -1
  43. package/dist/src/runtime/emitters.js +6 -0
  44. package/dist/src/runtime/emitters.js.map +1 -1
  45. package/dist/src/runtime/parallel-driver.d.ts +5 -0
  46. package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
  47. package/dist/src/runtime/parallel-driver.js +6 -0
  48. package/dist/src/runtime/parallel-driver.js.map +1 -1
  49. package/dist/src/runtime/scheduler.d.ts +9 -0
  50. package/dist/src/runtime/scheduler.d.ts.map +1 -1
  51. package/dist/src/runtime/scheduler.js +27 -11
  52. package/dist/src/runtime/scheduler.js.map +1 -1
  53. package/dist/src/runtime/types.d.ts +19 -0
  54. package/dist/src/runtime/types.d.ts.map +1 -1
  55. package/dist/src/runtime/types.js.map +1 -1
  56. package/dist/src/runtime/work-counters.d.ts +180 -0
  57. package/dist/src/runtime/work-counters.d.ts.map +1 -0
  58. package/dist/src/runtime/work-counters.js +210 -0
  59. package/dist/src/runtime/work-counters.js.map +1 -0
  60. package/dist/src/vtab/alter-event-shape.d.ts +35 -0
  61. package/dist/src/vtab/alter-event-shape.d.ts.map +1 -0
  62. package/dist/src/vtab/alter-event-shape.js +47 -0
  63. package/dist/src/vtab/alter-event-shape.js.map +1 -0
  64. package/dist/src/vtab/best-access-plan.d.ts +34 -0
  65. package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
  66. package/dist/src/vtab/best-access-plan.js +64 -0
  67. package/dist/src/vtab/best-access-plan.js.map +1 -1
  68. package/dist/src/vtab/index-descriptor.d.ts +9 -0
  69. package/dist/src/vtab/index-descriptor.d.ts.map +1 -1
  70. package/dist/src/vtab/index-descriptor.js +15 -0
  71. package/dist/src/vtab/index-descriptor.js.map +1 -1
  72. package/dist/src/vtab/memory/layer/manager.d.ts +11 -9
  73. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  74. package/dist/src/vtab/memory/layer/manager.js +12 -43
  75. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  76. package/dist/src/vtab/memory/module.d.ts +18 -8
  77. package/dist/src/vtab/memory/module.d.ts.map +1 -1
  78. package/dist/src/vtab/memory/module.js +168 -49
  79. package/dist/src/vtab/memory/module.js.map +1 -1
  80. package/dist/src/vtab/memory/table.d.ts +27 -3
  81. package/dist/src/vtab/memory/table.d.ts.map +1 -1
  82. package/dist/src/vtab/memory/table.js +28 -59
  83. package/dist/src/vtab/memory/table.js.map +1 -1
  84. package/package.json +6 -3
@@ -0,0 +1,180 @@
1
+ import type { OutputValue } from '../common/types.js';
2
+ import type { PlanNode } from '../planner/nodes/plan-node.js';
3
+ import type { PlanNodeType } from '../planner/nodes/plan-node-type.js';
4
+ import type { Scheduler } from './scheduler.js';
5
+ /**
6
+ * Machine-independent work counters for one statement execution.
7
+ *
8
+ * Every number here is a *count* — executions, input values, output rows — chosen
9
+ * because counts come out identical on every machine and every run of the same plan,
10
+ * where timings do not. `elapsedNs` is deliberately absent: putting a nanosecond
11
+ * figure on a surface whose premise is machine-independence invites exactly the
12
+ * cross-machine comparison this surface exists to replace.
13
+ *
14
+ * Instruction identity is a structural program address (see
15
+ * {@link WorkCounterCollector}), never a plan-node id: `PlanNode.id` is a
16
+ * process-global counter, so two runs of the same query in one process get different
17
+ * ids and any id-derived key would diverge immediately.
18
+ */
19
+ /** Plan-shape facts, available after compile without executing. */
20
+ export interface PlanShape {
21
+ nodeCount: number;
22
+ /** PlanNodeType -> how many plan nodes of that type. Keys sorted. */
23
+ nodeTypes: Record<string, number>;
24
+ }
25
+ /**
26
+ * Engine-to-module access counts for one table within one execution.
27
+ *
28
+ * These measure the **engine-to-module** boundary — the calls the runtime itself
29
+ * issued — not the **module-to-storage** boundary. A module that swaps 1000 single-key
30
+ * reads for one batched multi-key read moves neither number here: the engine issued the
31
+ * same one `query()` either way. Counting inside a module is a separate instrument
32
+ * (the counting key-value store double in `@quereus/store/testing`).
33
+ */
34
+ export interface TableWorkCounters {
35
+ /** `query()` calls the engine issued against this table. */
36
+ queryCalls: number;
37
+ /** Rows the engine pulled out of those `query()` calls. */
38
+ rowsScanned: number;
39
+ /** `update()` calls the engine issued — insert, update and delete alike. */
40
+ updateCalls: number;
41
+ }
42
+ /** JSON-serializable snapshot of one execution's work counters: no bigint, no timings. */
43
+ export interface WorkCounterSnapshot {
44
+ /** Plan-shape facts. Available after compile — no execution needed. */
45
+ plan: PlanShape;
46
+ /** One entry per instruction that ran at least once, in program-walk order. */
47
+ instructions: Array<{
48
+ /** Structural address, e.g. `r#12` or `r/12/0#3`. Never contains a plan-node id. */
49
+ key: string;
50
+ /** PlanNodeType, when the instruction came from a plan node. */
51
+ nodeType?: string;
52
+ /** Times this instruction's `run` was invoked. */
53
+ executions: number;
54
+ /**
55
+ * Argument VALUES received, summed over executions — NOT input rows: a
56
+ * streaming argument counts 1 however many rows flow through it (its row
57
+ * count is the producing instruction's `out`). So `in` and `out` of one
58
+ * entry are on different scales; don't read `out/in` as selectivity.
59
+ */
60
+ in: number;
61
+ /** Rows/values produced, summed over executions — per yield for streams. */
62
+ out: number;
63
+ }>;
64
+ /**
65
+ * Engine-to-module access, keyed `<schema>.<table>` (lowercased), keys sorted.
66
+ *
67
+ * Keyed by TABLE, not by scan site: a self-join scans one table from two sites and
68
+ * both roll into one entry — the per-site breakdown is already in `instructions`.
69
+ * A table the execution never touched is absent (same claim-vs-silence rule the
70
+ * instruction list follows).
71
+ */
72
+ tables: Record<string, TableWorkCounters>;
73
+ totals: {
74
+ instructionExecutions: number;
75
+ rowsOut: number;
76
+ /** `query()` calls summed over every table. */
77
+ queryCalls: number;
78
+ /** Rows pulled out of those calls, summed over every table. */
79
+ rowsScanned: number;
80
+ /** `update()` calls summed over every table. */
81
+ updateCalls: number;
82
+ };
83
+ }
84
+ /** Mutable per-instruction counter cell; the scheduler's metrics hooks feed these. */
85
+ export interface WorkCounterSlot {
86
+ /** Structural address (`<program-path>#<instruction-index>`); never a plan-node id. */
87
+ readonly key: string;
88
+ /**
89
+ * PlanNodeType stamped by `emitPlanNode` — the operator that PRODUCED the
90
+ * instruction, so a transparent wrapper (alias, collate, …) reports its source's
91
+ * type. Undefined for synthetic instructions (emitCall callbacks, fused scalars).
92
+ */
93
+ readonly nodeType: PlanNodeType | undefined;
94
+ executions: number;
95
+ in: number;
96
+ out: number;
97
+ }
98
+ /**
99
+ * Records one instruction output into `slot` and returns the value to park.
100
+ *
101
+ * - An async iterable is wrapped in a counting generator that increments `out` per
102
+ * yield — the scheduler's `countOutputs` reports 1 for a stream, and a "row count"
103
+ * that says 1 for every streaming operator is not a row count.
104
+ * - An array adds its length; everything else adds 1 (matching `countOutputs`).
105
+ *
106
+ * The wrap is skipped only when THIS slot already wrapped the iterable: a
107
+ * pass-through iterable re-counted by a *different* instruction's slot is correct
108
+ * double-wrapping (rows flow through both instructions), not a bug. Metrics mode and
109
+ * tracing mode are mutually exclusive in `Scheduler.run()` (metrics wins), so there
110
+ * is no interplay with the tracing wrapper's own marker symbol.
111
+ *
112
+ * NOTE: the wrap turns a re-iterable output (an object whose `[Symbol.asyncIterator]()`
113
+ * can be called more than once) into a single-shot generator, so a second pass over it
114
+ * would yield nothing WITH METRICS ON only. Safe today: every re-drive in the runtime
115
+ * goes through an `emitCall` callback that returns a fresh iterable per call, so no
116
+ * instruction output is iterated twice. If an emitter ever parks a re-iterable as its
117
+ * output, wrap per `[Symbol.asyncIterator]()` call instead of once per value.
118
+ */
119
+ export declare function recordOutput(slot: WorkCounterSlot, value: OutputValue): OutputValue;
120
+ /**
121
+ * Per-execution collector of work counters for a root scheduler and every
122
+ * sub-program reachable from it.
123
+ *
124
+ * Built once per statement execution (in `Statement._iterateRowsRawInternal`) and
125
+ * carried on the `RuntimeContext` (fork policy `shared-sink`: forks share it by
126
+ * reference, so counts from a forked branch roll up with no merge step). The
127
+ * scheduler's metrics hooks look up their slot array via {@link countersFor} once
128
+ * per program run.
129
+ *
130
+ * Instruction identity is structural: the root program is `r`, and the scheduler at
131
+ * `instructions[i].programs[j]` of program `P` is addressed `P/i/j`; instruction `i`
132
+ * of a program keys as `<path>#<i>`. This depends only on the shape of the compiled
133
+ * plan — never on execution order, which branch ran, or any plan-node id — so two
134
+ * runs of the same plan produce the same keys on any machine.
135
+ *
136
+ * Unlike `Instruction.runtimeStats` (zeroed per program invocation for the debug
137
+ * log), these slots are NEVER reset mid-execution: a correlated subquery's
138
+ * sub-program driven 100 times reports 100 executions of its inner instructions —
139
+ * the number that makes an N+1 regression visible.
140
+ */
141
+ export declare class WorkCounterCollector {
142
+ /** Slot arrays keyed by Scheduler identity — root program and every sub-program. */
143
+ private readonly slots;
144
+ /** Every slot in deterministic walk order (each sub-program right after its owning instruction). */
145
+ private readonly walkOrder;
146
+ /**
147
+ * Engine-to-module access cells, keyed `<schema>.<table>` (lowercased) and created
148
+ * on first access. Unlike the instruction slots this is NOT pre-walked: which tables
149
+ * an execution actually touches is a run-time fact (a branch never taken never
150
+ * connects), and an entry that exists means "the engine called this table".
151
+ */
152
+ private readonly tables;
153
+ constructor(root: Scheduler);
154
+ private walk;
155
+ /** The slot array for one program, index-aligned with `scheduler.instructions`. */
156
+ countersFor(scheduler: Scheduler): WorkCounterSlot[] | undefined;
157
+ /**
158
+ * The engine-to-module cell for one table, created zeroed on first access.
159
+ *
160
+ * `key` must be the lowercased `<schema>.<table>` the table carries AT RUN TIME —
161
+ * the post-remap name when a deferred constraint evaluates a plan frozen before an
162
+ * `ALTER TABLE ... RENAME TO` — so one table never splits across two entries.
163
+ */
164
+ tableCounters(key: string): TableWorkCounters;
165
+ /**
166
+ * Builds a fresh, by-value snapshot: instructions that never ran are omitted
167
+ * (a missing snapshot and an all-zero one are different claims — see
168
+ * `Statement.getWorkCounters`), and totals sum over the included entries only.
169
+ * Table entries exist only for tables the engine actually called, on the same rule.
170
+ */
171
+ snapshot(plan: PlanShape): WorkCounterSnapshot;
172
+ }
173
+ /**
174
+ * Counts plan nodes and their types with an iterative walk over `getChildren()` and
175
+ * a visited set — NOT `PlanNode.visit()`, which deliberately visits a DAG node once
176
+ * per *path* and would double-count shared subtrees (inflating `nodeCount`
177
+ * nondeterministically-looking across otherwise-identical plans).
178
+ */
179
+ export declare function computePlanShape(root: PlanNode): PlanShape;
180
+ //# sourceMappingURL=work-counters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work-counters.d.ts","sourceRoot":"","sources":["../../../src/runtime/work-counters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD;;;;;;;;;;;;;GAaG;AAEH,mEAAmE;AACnE,MAAM,WAAW,SAAS;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,0FAA0F;AAC1F,MAAM,WAAW,mBAAmB;IACnC,uEAAuE;IACvE,IAAI,EAAE,SAAS,CAAC;IAChB,+EAA+E;IAC/E,YAAY,EAAE,KAAK,CAAC;QACnB,oFAAoF;QACpF,GAAG,EAAE,MAAM,CAAC;QACZ,gEAAgE;QAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,kDAAkD;QAClD,UAAU,EAAE,MAAM,CAAC;QACnB;;;;;WAKG;QACH,EAAE,EAAE,MAAM,CAAC;QACX,4EAA4E;QAC5E,GAAG,EAAE,MAAM,CAAC;KACZ,CAAC,CAAC;IACH;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC1C,MAAM,EAAE;QACP,qBAAqB,EAAE,MAAM,CAAC;QAC9B,OAAO,EAAE,MAAM,CAAC;QAChB,+CAA+C;QAC/C,UAAU,EAAE,MAAM,CAAC;QACnB,+DAA+D;QAC/D,WAAW,EAAE,MAAM,CAAC;QACpB,gDAAgD;QAChD,WAAW,EAAE,MAAM,CAAC;KACpB,CAAC;CACF;AAED,sFAAsF;AACtF,MAAM,WAAW,eAAe;IAC/B,uFAAuF;IACvF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,CAAC;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACZ;AAKD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,GAAG,WAAW,CAqBnF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,oBAAoB;IAChC,oFAAoF;IACpF,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2C;IACjE,oGAAoG;IACpG,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwC;gBAEnD,IAAI,EAAE,SAAS;IAI3B,OAAO,CAAC,IAAI;IAgCZ,mFAAmF;IACnF,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,eAAe,EAAE,GAAG,SAAS;IAIhE;;;;;;OAMG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB;IAS7C;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,mBAAmB;CAwC9C;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,CAoB1D"}
@@ -0,0 +1,210 @@
1
+ import { isAsyncIterable } from './utils.js';
2
+ /** Marker linking a counting-wrapped iterable back to the slot that wrapped it. */
3
+ const COUNTED_ITERABLE_SYMBOL = Symbol('workCountedIterable');
4
+ /**
5
+ * Records one instruction output into `slot` and returns the value to park.
6
+ *
7
+ * - An async iterable is wrapped in a counting generator that increments `out` per
8
+ * yield — the scheduler's `countOutputs` reports 1 for a stream, and a "row count"
9
+ * that says 1 for every streaming operator is not a row count.
10
+ * - An array adds its length; everything else adds 1 (matching `countOutputs`).
11
+ *
12
+ * The wrap is skipped only when THIS slot already wrapped the iterable: a
13
+ * pass-through iterable re-counted by a *different* instruction's slot is correct
14
+ * double-wrapping (rows flow through both instructions), not a bug. Metrics mode and
15
+ * tracing mode are mutually exclusive in `Scheduler.run()` (metrics wins), so there
16
+ * is no interplay with the tracing wrapper's own marker symbol.
17
+ *
18
+ * NOTE: the wrap turns a re-iterable output (an object whose `[Symbol.asyncIterator]()`
19
+ * can be called more than once) into a single-shot generator, so a second pass over it
20
+ * would yield nothing WITH METRICS ON only. Safe today: every re-drive in the runtime
21
+ * goes through an `emitCall` callback that returns a fresh iterable per call, so no
22
+ * instruction output is iterated twice. If an emitter ever parks a re-iterable as its
23
+ * output, wrap per `[Symbol.asyncIterator]()` call instead of once per value.
24
+ */
25
+ export function recordOutput(slot, value) {
26
+ if (isAsyncIterable(value)) {
27
+ if (value[COUNTED_ITERABLE_SYMBOL] === slot) {
28
+ return value;
29
+ }
30
+ const source = value;
31
+ const wrapped = (async function* () {
32
+ for await (const item of source) {
33
+ slot.out++;
34
+ yield item;
35
+ }
36
+ })();
37
+ wrapped[COUNTED_ITERABLE_SYMBOL] = slot;
38
+ return wrapped;
39
+ }
40
+ if (Array.isArray(value)) {
41
+ slot.out += value.length;
42
+ return value;
43
+ }
44
+ slot.out += 1;
45
+ return value;
46
+ }
47
+ /**
48
+ * Per-execution collector of work counters for a root scheduler and every
49
+ * sub-program reachable from it.
50
+ *
51
+ * Built once per statement execution (in `Statement._iterateRowsRawInternal`) and
52
+ * carried on the `RuntimeContext` (fork policy `shared-sink`: forks share it by
53
+ * reference, so counts from a forked branch roll up with no merge step). The
54
+ * scheduler's metrics hooks look up their slot array via {@link countersFor} once
55
+ * per program run.
56
+ *
57
+ * Instruction identity is structural: the root program is `r`, and the scheduler at
58
+ * `instructions[i].programs[j]` of program `P` is addressed `P/i/j`; instruction `i`
59
+ * of a program keys as `<path>#<i>`. This depends only on the shape of the compiled
60
+ * plan — never on execution order, which branch ran, or any plan-node id — so two
61
+ * runs of the same plan produce the same keys on any machine.
62
+ *
63
+ * Unlike `Instruction.runtimeStats` (zeroed per program invocation for the debug
64
+ * log), these slots are NEVER reset mid-execution: a correlated subquery's
65
+ * sub-program driven 100 times reports 100 executions of its inner instructions —
66
+ * the number that makes an N+1 regression visible.
67
+ */
68
+ export class WorkCounterCollector {
69
+ /** Slot arrays keyed by Scheduler identity — root program and every sub-program. */
70
+ slots = new Map();
71
+ /** Every slot in deterministic walk order (each sub-program right after its owning instruction). */
72
+ walkOrder = [];
73
+ /**
74
+ * Engine-to-module access cells, keyed `<schema>.<table>` (lowercased) and created
75
+ * on first access. Unlike the instruction slots this is NOT pre-walked: which tables
76
+ * an execution actually touches is a run-time fact (a branch never taken never
77
+ * connects), and an entry that exists means "the engine called this table".
78
+ */
79
+ tables = new Map();
80
+ constructor(root) {
81
+ this.walk(root, 'r');
82
+ }
83
+ walk(scheduler, path) {
84
+ // Defensive: a scheduler reachable via two paths keeps its first path's keys
85
+ // (counters are per Scheduler identity, so both paths report under one key).
86
+ if (this.slots.has(scheduler))
87
+ return;
88
+ const programSlots = [];
89
+ this.slots.set(scheduler, programSlots);
90
+ for (let i = 0; i < scheduler.instructions.length; i++) {
91
+ const instruction = scheduler.instructions[i];
92
+ const slot = {
93
+ key: `${path}#${i}`,
94
+ nodeType: instruction.nodeType,
95
+ executions: 0,
96
+ in: 0,
97
+ out: 0,
98
+ };
99
+ programSlots.push(slot);
100
+ this.walkOrder.push(slot);
101
+ // NOTE: assumes `Instruction.programs` is only ever set by `emitCall`
102
+ // (runtime/emitters.ts), today's single site, and set at EMIT time — the walk
103
+ // runs once at execution start, so a Scheduler built during the run is absent
104
+ // from the map and `countersFor` returns undefined, silently counting nothing
105
+ // for it. The walk stays correct if a future emitter sets `programs` directly
106
+ // at emit time — addressing is purely structural — but that emitter's
107
+ // sub-programs silently start being counted here too.
108
+ if (instruction.programs) {
109
+ for (let j = 0; j < instruction.programs.length; j++) {
110
+ this.walk(instruction.programs[j], `${path}/${i}/${j}`);
111
+ }
112
+ }
113
+ }
114
+ }
115
+ /** The slot array for one program, index-aligned with `scheduler.instructions`. */
116
+ countersFor(scheduler) {
117
+ return this.slots.get(scheduler);
118
+ }
119
+ /**
120
+ * The engine-to-module cell for one table, created zeroed on first access.
121
+ *
122
+ * `key` must be the lowercased `<schema>.<table>` the table carries AT RUN TIME —
123
+ * the post-remap name when a deferred constraint evaluates a plan frozen before an
124
+ * `ALTER TABLE ... RENAME TO` — so one table never splits across two entries.
125
+ */
126
+ tableCounters(key) {
127
+ let entry = this.tables.get(key);
128
+ if (entry === undefined) {
129
+ entry = { queryCalls: 0, rowsScanned: 0, updateCalls: 0 };
130
+ this.tables.set(key, entry);
131
+ }
132
+ return entry;
133
+ }
134
+ /**
135
+ * Builds a fresh, by-value snapshot: instructions that never ran are omitted
136
+ * (a missing snapshot and an all-zero one are different claims — see
137
+ * `Statement.getWorkCounters`), and totals sum over the included entries only.
138
+ * Table entries exist only for tables the engine actually called, on the same rule.
139
+ */
140
+ snapshot(plan) {
141
+ const instructions = [];
142
+ let instructionExecutions = 0;
143
+ let rowsOut = 0;
144
+ for (const slot of this.walkOrder) {
145
+ if (slot.executions === 0)
146
+ continue;
147
+ const entry = {
148
+ key: slot.key,
149
+ executions: slot.executions,
150
+ in: slot.in,
151
+ out: slot.out,
152
+ };
153
+ if (slot.nodeType !== undefined) {
154
+ entry.nodeType = slot.nodeType;
155
+ }
156
+ instructions.push(entry);
157
+ instructionExecutions += slot.executions;
158
+ rowsOut += slot.out;
159
+ }
160
+ // Sorted keys so two snapshots of the same execution serialize identically —
161
+ // Map iteration order is insertion order, which is scan-connect order, which a
162
+ // plan change can reshuffle without changing any count.
163
+ const tables = {};
164
+ let queryCalls = 0;
165
+ let rowsScanned = 0;
166
+ let updateCalls = 0;
167
+ for (const key of [...this.tables.keys()].sort()) {
168
+ const cell = this.tables.get(key);
169
+ tables[key] = { queryCalls: cell.queryCalls, rowsScanned: cell.rowsScanned, updateCalls: cell.updateCalls };
170
+ queryCalls += cell.queryCalls;
171
+ rowsScanned += cell.rowsScanned;
172
+ updateCalls += cell.updateCalls;
173
+ }
174
+ return {
175
+ plan: { nodeCount: plan.nodeCount, nodeTypes: { ...plan.nodeTypes } },
176
+ instructions,
177
+ tables,
178
+ totals: { instructionExecutions, rowsOut, queryCalls, rowsScanned, updateCalls },
179
+ };
180
+ }
181
+ }
182
+ /**
183
+ * Counts plan nodes and their types with an iterative walk over `getChildren()` and
184
+ * a visited set — NOT `PlanNode.visit()`, which deliberately visits a DAG node once
185
+ * per *path* and would double-count shared subtrees (inflating `nodeCount`
186
+ * nondeterministically-looking across otherwise-identical plans).
187
+ */
188
+ export function computePlanShape(root) {
189
+ const visited = new Set();
190
+ const stack = [root];
191
+ const counts = new Map();
192
+ let nodeCount = 0;
193
+ while (stack.length > 0) {
194
+ const node = stack.pop();
195
+ if (visited.has(node))
196
+ continue;
197
+ visited.add(node);
198
+ nodeCount++;
199
+ counts.set(node.nodeType, (counts.get(node.nodeType) ?? 0) + 1);
200
+ for (const child of node.getChildren()) {
201
+ stack.push(child);
202
+ }
203
+ }
204
+ const nodeTypes = {};
205
+ for (const key of [...counts.keys()].sort()) {
206
+ nodeTypes[key] = counts.get(key);
207
+ }
208
+ return { nodeCount, nodeTypes };
209
+ }
210
+ //# sourceMappingURL=work-counters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work-counters.js","sourceRoot":"","sources":["../../../src/runtime/work-counters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The per-arm shape of an `ALTER TABLE` schema-change announcement, derived from the
3
+ * {@link SchemaChangeInfo} the arm was called with. One derivation, shared by every
4
+ * emitter-backed module, so a subscriber sees the same facts regardless of backend.
5
+ */
6
+ import type { SchemaChangeInfo } from './module.js';
7
+ import type { VTableSchemaChangeEvent } from './events.js';
8
+ /** The fields of a schema-change event that an ALTER TABLE arm decides. */
9
+ export type AlterEventShape = Pick<VTableSchemaChangeEvent, 'type' | 'objectType' | 'columnName' | 'oldColumnName'>;
10
+ /**
11
+ * Derive the `type` / `objectType` / column naming of an `ALTER TABLE` announcement:
12
+ * `alter`/`column` naming the touched column for the column arms (`drop`/`column` for
13
+ * DROP COLUMN), `alter`/`table` for the whole-table ones. Matches what the engine's own
14
+ * no-emitter path reports for the same statements (`runtime/emit/alter-table.ts`) and
15
+ * what `docs/usage.md` § What each `ALTER TABLE` arm reports promises.
16
+ *
17
+ * The caller supplies the rest of the event (`schemaName`, `objectName`, `ddl`) and owns
18
+ * the emit-iff-`change.ddl` gate. `RENAME TO` is not an arm of this union — it goes
19
+ * through `renameTable` and reports `alter`/`table` with `oldObjectName`.
20
+ *
21
+ * The `switch` is exhaustive over the union so a new arm fails the build here rather
22
+ * than silently announcing the wrong shape.
23
+ *
24
+ * NOTE: this unifies the two EMITTER-BACKED producers (the memory module and the store).
25
+ * The engine's own fallback path is a third producer that still writes the same triples out
26
+ * by hand, one per arm, in `runtime/emit/alter-table.ts` — it emits at each arm's tail from
27
+ * per-arm locals, with no `SchemaChangeInfo` in scope to derive from. Drift between the two
28
+ * derivations is caught by the cross-backend parity spec (`@quereus/store`'s
29
+ * `test/alter-events.spec.ts`), so this is a duplication with a guard, not an open hole. If a
30
+ * fourth producer appears, or the arm union grows enough that the parity spec stops covering
31
+ * every arm, thread the `SchemaChangeInfo` down to those emit sites and delete the hand-written
32
+ * triples.
33
+ */
34
+ export declare function alterEventShape(change: SchemaChangeInfo): AlterEventShape;
35
+ //# sourceMappingURL=alter-event-shape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alter-event-shape.d.ts","sourceRoot":"","sources":["../../../src/vtab/alter-event-shape.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,2EAA2E;AAC3E,MAAM,MAAM,eAAe,GAAG,IAAI,CACjC,uBAAuB,EACvB,MAAM,GAAG,YAAY,GAAG,YAAY,GAAG,eAAe,CACtD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,eAAe,CAgBzE"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The per-arm shape of an `ALTER TABLE` schema-change announcement, derived from the
3
+ * {@link SchemaChangeInfo} the arm was called with. One derivation, shared by every
4
+ * emitter-backed module, so a subscriber sees the same facts regardless of backend.
5
+ */
6
+ /**
7
+ * Derive the `type` / `objectType` / column naming of an `ALTER TABLE` announcement:
8
+ * `alter`/`column` naming the touched column for the column arms (`drop`/`column` for
9
+ * DROP COLUMN), `alter`/`table` for the whole-table ones. Matches what the engine's own
10
+ * no-emitter path reports for the same statements (`runtime/emit/alter-table.ts`) and
11
+ * what `docs/usage.md` § What each `ALTER TABLE` arm reports promises.
12
+ *
13
+ * The caller supplies the rest of the event (`schemaName`, `objectName`, `ddl`) and owns
14
+ * the emit-iff-`change.ddl` gate. `RENAME TO` is not an arm of this union — it goes
15
+ * through `renameTable` and reports `alter`/`table` with `oldObjectName`.
16
+ *
17
+ * The `switch` is exhaustive over the union so a new arm fails the build here rather
18
+ * than silently announcing the wrong shape.
19
+ *
20
+ * NOTE: this unifies the two EMITTER-BACKED producers (the memory module and the store).
21
+ * The engine's own fallback path is a third producer that still writes the same triples out
22
+ * by hand, one per arm, in `runtime/emit/alter-table.ts` — it emits at each arm's tail from
23
+ * per-arm locals, with no `SchemaChangeInfo` in scope to derive from. Drift between the two
24
+ * derivations is caught by the cross-backend parity spec (`@quereus/store`'s
25
+ * `test/alter-events.spec.ts`), so this is a duplication with a guard, not an open hole. If a
26
+ * fourth producer appears, or the arm union grows enough that the parity spec stops covering
27
+ * every arm, thread the `SchemaChangeInfo` down to those emit sites and delete the hand-written
28
+ * triples.
29
+ */
30
+ export function alterEventShape(change) {
31
+ switch (change.type) {
32
+ case 'addColumn':
33
+ return { type: 'alter', objectType: 'column', columnName: change.columnDef.name };
34
+ case 'dropColumn':
35
+ return { type: 'drop', objectType: 'column', columnName: change.columnName };
36
+ case 'renameColumn':
37
+ return { type: 'alter', objectType: 'column', columnName: change.newName, oldColumnName: change.oldName };
38
+ case 'alterColumn':
39
+ return { type: 'alter', objectType: 'column', columnName: change.columnName };
40
+ case 'alterPrimaryKey':
41
+ case 'addConstraint':
42
+ case 'dropConstraint':
43
+ case 'renameConstraint':
44
+ return { type: 'alter', objectType: 'table' };
45
+ }
46
+ }
47
+ //# sourceMappingURL=alter-event-shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alter-event-shape.js","sourceRoot":"","sources":["../../../src/vtab/alter-event-shape.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,eAAe,CAAC,MAAwB;IACvD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,WAAW;YACf,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QACnF,KAAK,YAAY;YAChB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;QAC9E,KAAK,cAAc;YAClB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;QAC3G,KAAK,aAAa;YACjB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;QAC/E,KAAK,iBAAiB,CAAC;QACvB,KAAK,eAAe,CAAC;QACrB,KAAK,gBAAgB,CAAC;QACtB,KAAK,kBAAkB;YACtB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;IAChD,CAAC;AACF,CAAC"}
@@ -5,6 +5,7 @@
5
5
  import { type SqlValue, type Row } from '../common/types.js';
6
6
  import type { LogicalType } from '../types/logical-type.js';
7
7
  import { type IndexDescriptor } from './index-descriptor.js';
8
+ import type { IndexColumnSchema, TableSchema } from '../schema/table.js';
8
9
  /**
9
10
  * Constraint operators that can be pushed down to virtual tables
10
11
  */
@@ -128,6 +129,39 @@ export declare function equalitySeekKeyCount(f: PredicateConstraint): number | n
128
129
  * one fills no equality role at all, so it is not multi-valued either.
129
130
  */
130
131
  export declare function isMultiValueEquality(f: PredicateConstraint): boolean;
132
+ /**
133
+ * Truncate an ordered key's order-preserving prefix at the first DESC column whose walk
134
+ * could misplace NULLs. Shared by every module that turns an index (or primary-key) walk
135
+ * into an ordering claim.
136
+ *
137
+ * The engine's ORDER BY places NULLs FIRST for BOTH directions — placement is absolute,
138
+ * never direction-conditioned (`orderByNullResult`, util/comparison.ts). Both shipped
139
+ * backends agree on an ASC column and DISAGREE on a DESC one: the store bit-inverts the
140
+ * column's key bytes (NULL's low `0x00` tag ends up last) and the memory module negates
141
+ * the ascending comparator (NULL is the lowest value, so negation sends it last). So a
142
+ * DESC ordering claim is only sound when no NULL can appear in that column's emitted
143
+ * rows:
144
+ *
145
+ * - the column is declared NOT NULL — columns are NOT NULL by default in this engine, so
146
+ * this is the case that keeps the gate from firing on ordinary tables;
147
+ * - the column is pinned by this plan's own equality (`pinnedCols`) — an equality never
148
+ * matches NULL, and a pinned column contributes no ordering anyway;
149
+ * - some pushed filter on the column is NULL-excluding ({@link NULL_EXCLUDING_OPS}) —
150
+ * this is what keeps `where n > 5 order by n desc` fast, and it holds whether or not
151
+ * the module marks the filter handled: an unhandled filter is retained by the engine
152
+ * as a residual `Filter`, which removes the NULL rows and preserves order either way.
153
+ *
154
+ * `keyColumns` is `ReadonlyArray<IndexColumnSchema>` rather than a whole `IndexSchema` so
155
+ * that a table's `primaryKeyDefinition` can be passed directly — `PrimaryKeyColumnDefinition`
156
+ * is structurally identical (`{ index; desc?; collation? }`) and a nullable DESC primary-key
157
+ * member is exposed by exactly the same reasoning as a secondary-index one.
158
+ *
159
+ * The prefix-length form is the primitive. A caller that claims a required ordering
160
+ * VERBATIM (all or nothing, which is what both backends' request-matching paths do) wants
161
+ * the boolean `nullSafeOrderingPrefixLength(...) === matchedLength`; truncation only
162
+ * matters where an index's OWN ordering is advertised with no request to match.
163
+ */
164
+ export declare function nullSafeOrderingPrefixLength(tableInfo: TableSchema, request: BestAccessPlanRequest, keyColumns: ReadonlyArray<IndexColumnSchema>, orderPreservingPrefix: number, pinnedCols: ReadonlySet<number>): number;
131
165
  /**
132
166
  * Ordering specification for ORDER BY clauses
133
167
  */
@@ -1 +1 @@
1
- {"version":3,"file":"best-access-plan.d.ts","sourceRoot":"","sources":["../../../src/vtab/best-access-plan.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAc,KAAK,QAAQ,EAAE,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,IAAI,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEhJ;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,qDAAqD;IACrD,YAAY,EAAE,OAAO,CAAC;IACtB,kDAAkD;IAClD,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACzB,KAAK,CAAC,EAAE;QAAE,EAAE,EAAE,IAAI,GAAG,GAAG,CAAC;QAAC,KAAK,EAAE,QAAQ,CAAA;KAAE,CAAC;IAC5C,KAAK,CAAC,EAAE;QAAE,EAAE,EAAE,IAAI,GAAG,GAAG,CAAC;QAAC,KAAK,EAAE,QAAQ,CAAA;KAAE,CAAC;CAC5C;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;OASG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,EAAE,EAAE,YAAY,CAAC;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,+DAA+D;IAC/D,MAAM,EAAE,OAAO,CAAC;IAChB,oDAAoD;IACpD,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAY1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAIpE;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,IAAI,EAAE,OAAO,CAAC;IACd,oDAAoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,oCAAoC;IACpC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IAC/B,wDAAwD;IACxD,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACxC,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,kFAAkF;IAClF,cAAc,EAAE,SAAS,OAAO,EAAE,CAAC;IACnC,kEAAkE;IAClE,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;IACvC,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,6EAA6E;IAC7E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;;;;;;;;;OAaG;IACH,WAAW,CAAC,EAAE;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,KAAK,GAAG,MAAM,CAAC;QAC1B,MAAM,EAAE,OAAO,CAAC;KAChB,CAAC;IAEF;;;;;;;;;;;;;;;;;;;OAmBG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;IAEpC;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAC7B,OAAO,CAAC,MAAM,CAAqC;IAEnD;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,iBAAiB;IAOzD;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,GAAE,MAAY,GAAG,iBAAiB;IAQ/E;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,GAAE,MAAY,GAAG,iBAAiB;IAOnF;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK3B;;;;;;;;OAQG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ5B;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAKvC;;OAEG;IACH,iBAAiB,CAAC,cAAc,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI;IAK3D;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAAG,IAAI;IAKpD;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAK9B;;OAEG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAKzC;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKrC;;;OAGG;IACH,kBAAkB,CAAC,eAAe,EAAE,eAAe,GAAG,IAAI;IAK1D;;OAEG;IACH,cAAc,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAK1D;;OAEG;IACH,iBAAiB,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,GAAG,IAAI;IAKtD;;OAEG;IACH,KAAK,IAAI,oBAAoB;CAW7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CA8B9E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,oBAAoB,GAC1B,IAAI,CAwGN"}
1
+ {"version":3,"file":"best-access-plan.d.ts","sourceRoot":"","sources":["../../../src/vtab/best-access-plan.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAc,KAAK,QAAQ,EAAE,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,IAAI,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEhJ;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,qDAAqD;IACrD,YAAY,EAAE,OAAO,CAAC;IACtB,kDAAkD;IAClD,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACzB,KAAK,CAAC,EAAE;QAAE,EAAE,EAAE,IAAI,GAAG,GAAG,CAAC;QAAC,KAAK,EAAE,QAAQ,CAAA;KAAE,CAAC;IAC5C,KAAK,CAAC,EAAE;QAAE,EAAE,EAAE,IAAI,GAAG,GAAG,CAAC;QAAC,KAAK,EAAE,QAAQ,CAAA;KAAE,CAAC;CAC5C;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;OASG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,EAAE,EAAE,YAAY,CAAC;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,+DAA+D;IAC/D,MAAM,EAAE,OAAO,CAAC;IAChB,oDAAoD;IACpD,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAY1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAIpE;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,4BAA4B,CAC3C,SAAS,EAAE,WAAW,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,EAAE,aAAa,CAAC,iBAAiB,CAAC,EAC5C,qBAAqB,EAAE,MAAM,EAC7B,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,GAC7B,MAAM,CAUR;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,IAAI,EAAE,OAAO,CAAC;IACd,oDAAoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,oCAAoC;IACpC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IAC/B,wDAAwD;IACxD,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACxC,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,kFAAkF;IAClF,cAAc,EAAE,SAAS,OAAO,EAAE,CAAC;IACnC,kEAAkE;IAClE,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;IACvC,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,6EAA6E;IAC7E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;;;;;;;;;OAaG;IACH,WAAW,CAAC,EAAE;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,KAAK,GAAG,MAAM,CAAC;QAC1B,MAAM,EAAE,OAAO,CAAC;KAChB,CAAC;IAEF;;;;;;;;;;;;;;;;;;;OAmBG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;IAEpC;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAC7B,OAAO,CAAC,MAAM,CAAqC;IAEnD;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,iBAAiB;IAOzD;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,GAAE,MAAY,GAAG,iBAAiB;IAQ/E;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,GAAE,MAAY,GAAG,iBAAiB;IAOnF;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK3B;;;;;;;;OAQG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ5B;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAKvC;;OAEG;IACH,iBAAiB,CAAC,cAAc,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI;IAK3D;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAAG,IAAI;IAKpD;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAK9B;;OAEG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAKzC;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKrC;;;OAGG;IACH,kBAAkB,CAAC,eAAe,EAAE,eAAe,GAAG,IAAI;IAK1D;;OAEG;IACH,cAAc,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAK1D;;OAEG;IACH,iBAAiB,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,GAAG,IAAI;IAKtD;;OAEG;IACH,KAAK,IAAI,oBAAoB;CAW7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CA8B9E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,oBAAoB,GAC1B,IAAI,CAwGN"}
@@ -61,6 +61,70 @@ export function isMultiValueEquality(f) {
61
61
  return equalitySeekKeyCount(f) !== null;
62
62
  return Array.isArray(f.value) && f.value.length > 1;
63
63
  }
64
+ /**
65
+ * Pushed-filter operators that reject a NULL in the filtered column: every comparison and
66
+ * IN-membership evaluates to NULL (not TRUE) against a NULL operand, and `IS NOT NULL`
67
+ * says so outright. A row with NULL in that column therefore cannot survive the filter —
68
+ * wherever the filter ends up being enforced.
69
+ *
70
+ * The set is deliberately INCOMPLETE, which costs an optimization and never correctness —
71
+ * a missing op declines a claim that would in fact have been sound. `OR_RANGE` is a union
72
+ * of NULL-rejecting ranges, but it already disqualifies an ordering claim on other grounds
73
+ * in both shipped backends (concatenated windows emit in range order, not column order),
74
+ * so listing it would widen what has to be argued and buy nothing. `NOT IN`, `MATCH`,
75
+ * `LIKE` and `GLOB` are NULL-rejecting too and could be added on demand; none of them
76
+ * reaches an ordering claim today.
77
+ */
78
+ const NULL_EXCLUDING_OPS = new Set([
79
+ '=', 'IN', '>', '>=', '<', '<=', 'IS NOT NULL',
80
+ ]);
81
+ /**
82
+ * Truncate an ordered key's order-preserving prefix at the first DESC column whose walk
83
+ * could misplace NULLs. Shared by every module that turns an index (or primary-key) walk
84
+ * into an ordering claim.
85
+ *
86
+ * The engine's ORDER BY places NULLs FIRST for BOTH directions — placement is absolute,
87
+ * never direction-conditioned (`orderByNullResult`, util/comparison.ts). Both shipped
88
+ * backends agree on an ASC column and DISAGREE on a DESC one: the store bit-inverts the
89
+ * column's key bytes (NULL's low `0x00` tag ends up last) and the memory module negates
90
+ * the ascending comparator (NULL is the lowest value, so negation sends it last). So a
91
+ * DESC ordering claim is only sound when no NULL can appear in that column's emitted
92
+ * rows:
93
+ *
94
+ * - the column is declared NOT NULL — columns are NOT NULL by default in this engine, so
95
+ * this is the case that keeps the gate from firing on ordinary tables;
96
+ * - the column is pinned by this plan's own equality (`pinnedCols`) — an equality never
97
+ * matches NULL, and a pinned column contributes no ordering anyway;
98
+ * - some pushed filter on the column is NULL-excluding ({@link NULL_EXCLUDING_OPS}) —
99
+ * this is what keeps `where n > 5 order by n desc` fast, and it holds whether or not
100
+ * the module marks the filter handled: an unhandled filter is retained by the engine
101
+ * as a residual `Filter`, which removes the NULL rows and preserves order either way.
102
+ *
103
+ * `keyColumns` is `ReadonlyArray<IndexColumnSchema>` rather than a whole `IndexSchema` so
104
+ * that a table's `primaryKeyDefinition` can be passed directly — `PrimaryKeyColumnDefinition`
105
+ * is structurally identical (`{ index; desc?; collation? }`) and a nullable DESC primary-key
106
+ * member is exposed by exactly the same reasoning as a secondary-index one.
107
+ *
108
+ * The prefix-length form is the primitive. A caller that claims a required ordering
109
+ * VERBATIM (all or nothing, which is what both backends' request-matching paths do) wants
110
+ * the boolean `nullSafeOrderingPrefixLength(...) === matchedLength`; truncation only
111
+ * matters where an index's OWN ordering is advertised with no request to match.
112
+ */
113
+ export function nullSafeOrderingPrefixLength(tableInfo, request, keyColumns, orderPreservingPrefix, pinnedCols) {
114
+ for (let i = 0; i < orderPreservingPrefix; i++) {
115
+ const col = keyColumns[i];
116
+ if (!col.desc)
117
+ continue;
118
+ if (tableInfo.columns[col.index]?.notNull)
119
+ continue;
120
+ if (pinnedCols.has(col.index))
121
+ continue;
122
+ if (request.filters.some(f => f.columnIndex === col.index && NULL_EXCLUDING_OPS.has(f.op)))
123
+ continue;
124
+ return i;
125
+ }
126
+ return orderPreservingPrefix;
127
+ }
64
128
  /**
65
129
  * Builder class for constructing access plan results
66
130
  */