@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.
- package/dist/src/core/statement.d.ts +30 -0
- package/dist/src/core/statement.d.ts.map +1 -1
- package/dist/src/core/statement.js +49 -0
- package/dist/src/core/statement.js.map +1 -1
- package/dist/src/emit/ast-stringify.d.ts.map +1 -1
- package/dist/src/emit/ast-stringify.js +0 -4
- package/dist/src/emit/ast-stringify.js.map +1 -1
- package/dist/src/func/builtins/explain.d.ts.map +1 -1
- package/dist/src/func/builtins/explain.js +88 -61
- package/dist/src/func/builtins/explain.js.map +1 -1
- package/dist/src/func/registration.d.ts +7 -0
- package/dist/src/func/registration.d.ts.map +1 -1
- package/dist/src/func/registration.js +7 -0
- package/dist/src/func/registration.js.map +1 -1
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/parser/ast.d.ts +0 -2
- package/dist/src/parser/ast.d.ts.map +1 -1
- package/dist/src/parser/parser.d.ts +1 -0
- package/dist/src/parser/parser.d.ts.map +1 -1
- package/dist/src/parser/parser.js +29 -28
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.js +31 -7
- package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
- package/dist/src/planner/stats/analyze.d.ts.map +1 -1
- package/dist/src/planner/stats/analyze.js +9 -3
- package/dist/src/planner/stats/analyze.js.map +1 -1
- package/dist/src/runtime/emit/analyze.d.ts.map +1 -1
- package/dist/src/runtime/emit/analyze.js +1 -2
- package/dist/src/runtime/emit/analyze.js.map +1 -1
- package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
- package/dist/src/runtime/emit/dml-executor.js +23 -0
- package/dist/src/runtime/emit/dml-executor.js.map +1 -1
- package/dist/src/runtime/emit/scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/scan.js +52 -9
- package/dist/src/runtime/emit/scan.js.map +1 -1
- package/dist/src/runtime/emit/set-object-tags.js +4 -4
- package/dist/src/runtime/emit/set-object-tags.js.map +1 -1
- package/dist/src/runtime/emitters.d.ts.map +1 -1
- package/dist/src/runtime/emitters.js +6 -0
- package/dist/src/runtime/emitters.js.map +1 -1
- package/dist/src/runtime/parallel-driver.d.ts +5 -0
- package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
- package/dist/src/runtime/parallel-driver.js +6 -0
- package/dist/src/runtime/parallel-driver.js.map +1 -1
- package/dist/src/runtime/scheduler.d.ts +9 -0
- package/dist/src/runtime/scheduler.d.ts.map +1 -1
- package/dist/src/runtime/scheduler.js +27 -11
- package/dist/src/runtime/scheduler.js.map +1 -1
- package/dist/src/runtime/types.d.ts +19 -0
- package/dist/src/runtime/types.d.ts.map +1 -1
- package/dist/src/runtime/types.js.map +1 -1
- package/dist/src/runtime/work-counters.d.ts +180 -0
- package/dist/src/runtime/work-counters.d.ts.map +1 -0
- package/dist/src/runtime/work-counters.js +210 -0
- package/dist/src/runtime/work-counters.js.map +1 -0
- package/dist/src/vtab/alter-event-shape.d.ts +35 -0
- package/dist/src/vtab/alter-event-shape.d.ts.map +1 -0
- package/dist/src/vtab/alter-event-shape.js +47 -0
- package/dist/src/vtab/alter-event-shape.js.map +1 -0
- package/dist/src/vtab/best-access-plan.d.ts +34 -0
- package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
- package/dist/src/vtab/best-access-plan.js +64 -0
- package/dist/src/vtab/best-access-plan.js.map +1 -1
- package/dist/src/vtab/index-descriptor.d.ts +9 -0
- package/dist/src/vtab/index-descriptor.d.ts.map +1 -1
- package/dist/src/vtab/index-descriptor.js +15 -0
- package/dist/src/vtab/index-descriptor.js.map +1 -1
- package/dist/src/vtab/memory/layer/manager.d.ts +11 -9
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +12 -43
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/dist/src/vtab/memory/module.d.ts +18 -8
- package/dist/src/vtab/memory/module.d.ts.map +1 -1
- package/dist/src/vtab/memory/module.js +168 -49
- package/dist/src/vtab/memory/module.js.map +1 -1
- package/dist/src/vtab/memory/table.d.ts +27 -3
- package/dist/src/vtab/memory/table.d.ts.map +1 -1
- package/dist/src/vtab/memory/table.js +28 -59
- package/dist/src/vtab/memory/table.js.map +1 -1
- 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;
|
|
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
|
*/
|