@minnowdb/core 0.6.1 → 0.6.2
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/engine/artifact-cache.d.ts +5 -0
- package/dist/engine/artifact-cache.js +64 -13
- package/dist/engine/database.js +282 -7
- package/dist/engine/point-read.d.ts +57 -0
- package/dist/engine/point-read.js +189 -0
- package/dist/storage/toolkit/record-core.js +104 -75
- package/dist/storage/types.js +34 -6
- package/dist/transactions/index.js +9 -1
- package/package.json +1 -1
- package/sql-feature-matrix.json +52 -2
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape analysis for the keyed point-read fast path: a single-table conjunction of
|
|
3
|
+
* column-equals-literal predicates that covers the table's unique key, projecting plain
|
|
4
|
+
* columns. Such a statement addresses at most one row, so execution can skip parameter
|
|
5
|
+
* binding, plan cloning, streamed-view construction, and the vector pipeline entirely and
|
|
6
|
+
* answer from cached decoded blocks. Anything this module cannot prove eligible falls back to
|
|
7
|
+
* the ordinary executor, which stays the authority on errors and semantics.
|
|
8
|
+
*/
|
|
9
|
+
import { dateMilliseconds } from "../date-value.js";
|
|
10
|
+
/**
|
|
11
|
+
* Test-only escape hatch and counters. Not exported from any public entry point: in-repo
|
|
12
|
+
* differential suites import this module directly to force the ordinary executor and to
|
|
13
|
+
* assert the fast path actually served eligible statements.
|
|
14
|
+
*/
|
|
15
|
+
export const pointReadTestHooks = {
|
|
16
|
+
disabled: false,
|
|
17
|
+
attempted: 0,
|
|
18
|
+
served: 0,
|
|
19
|
+
};
|
|
20
|
+
const DUAL_TABLE = "(dual)";
|
|
21
|
+
/** A base-table column reference: bare, or qualified by the base source's alias. */
|
|
22
|
+
function baseColumnReference(expression, alias) {
|
|
23
|
+
if (expression.kind !== "column")
|
|
24
|
+
return undefined;
|
|
25
|
+
const reference = expression.reference;
|
|
26
|
+
const dot = reference.indexOf(".");
|
|
27
|
+
if (dot < 0)
|
|
28
|
+
return reference;
|
|
29
|
+
if (reference.slice(0, dot) !== alias)
|
|
30
|
+
return undefined;
|
|
31
|
+
const column = reference.slice(dot + 1);
|
|
32
|
+
// A nested qualifier ("a.b.c") is not a base-table column.
|
|
33
|
+
return column.includes(".") ? undefined : column;
|
|
34
|
+
}
|
|
35
|
+
/** A usable equality operand: a plain non-null literal of a storage type, verified exactly. */
|
|
36
|
+
function equalityValue(value) {
|
|
37
|
+
if (value === null)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (typeof value === "number" && !Number.isFinite(value))
|
|
40
|
+
return undefined;
|
|
41
|
+
// Strings in the engine's protected NUL namespace are wrapped at the write boundary, so a
|
|
42
|
+
// raw comparison against stored bytes would not reproduce the ordinary path's answer.
|
|
43
|
+
if (typeof value === "string" && value.startsWith("\u0000"))
|
|
44
|
+
return undefined;
|
|
45
|
+
// The internal-slot read never invokes caller-controlled Date methods.
|
|
46
|
+
if (value instanceof Date && Number.isNaN(dateMilliseconds(value)))
|
|
47
|
+
return undefined;
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
function templateEquality(expression) {
|
|
51
|
+
if (expression.kind === "parameter")
|
|
52
|
+
return { parameter: expression.index };
|
|
53
|
+
if (expression.kind !== "literal")
|
|
54
|
+
return undefined;
|
|
55
|
+
// Tagged internal values and logical-domain literals compare under domain rules the fast
|
|
56
|
+
// path does not implement; a NULL literal never equals anything.
|
|
57
|
+
if (expression.internalSqlValue === true || expression.sqlDomain !== undefined)
|
|
58
|
+
return undefined;
|
|
59
|
+
const value = equalityValue(expression.value);
|
|
60
|
+
return value === undefined ? undefined : { value };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Recognizes the eligible statement shape. Purely syntactic: catalog checks (column existence,
|
|
64
|
+
* types, key coverage, view expansion, physical history) belong to the caller, which falls
|
|
65
|
+
* back to the ordinary executor whenever they cannot be proven.
|
|
66
|
+
*/
|
|
67
|
+
function pointReadTemplate(plan) {
|
|
68
|
+
const base = plan.base;
|
|
69
|
+
if (base.table === DUAL_TABLE ||
|
|
70
|
+
base.derived !== undefined ||
|
|
71
|
+
base.union !== undefined ||
|
|
72
|
+
base.recursive !== undefined ||
|
|
73
|
+
base.windowed !== undefined ||
|
|
74
|
+
base.lateral === true ||
|
|
75
|
+
base.columnAliases !== undefined ||
|
|
76
|
+
plan.joins.length > 0 ||
|
|
77
|
+
plan.groupBy.length > 0 ||
|
|
78
|
+
plan.having.length > 0 ||
|
|
79
|
+
plan.orderBy.length > 0 ||
|
|
80
|
+
plan.select.length === 0 ||
|
|
81
|
+
plan.predicates.length === 0 ||
|
|
82
|
+
plan.limit !== undefined ||
|
|
83
|
+
plan.offset !== undefined ||
|
|
84
|
+
plan.limitParameter !== undefined ||
|
|
85
|
+
plan.offsetParameter !== undefined ||
|
|
86
|
+
(plan.limitValidationParameters?.length ?? 0) > 0 ||
|
|
87
|
+
(plan.offsetValidationParameters?.length ?? 0) > 0 ||
|
|
88
|
+
plan.limitWithTies === true ||
|
|
89
|
+
plan.distinctWildcard === true ||
|
|
90
|
+
plan.usesStatementDatetime === true ||
|
|
91
|
+
plan.usesSequenceCalls === true ||
|
|
92
|
+
plan.usesVolatileFunctions === true) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const select = [];
|
|
96
|
+
for (const item of plan.select) {
|
|
97
|
+
const column = baseColumnReference(item.expression, base.alias);
|
|
98
|
+
if (column === undefined)
|
|
99
|
+
return undefined;
|
|
100
|
+
select.push({ column, alias: item.alias });
|
|
101
|
+
}
|
|
102
|
+
const equalities = [];
|
|
103
|
+
for (const predicate of plan.predicates) {
|
|
104
|
+
if (predicate.operator !== "=" || predicate.escape !== undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
const leftColumn = baseColumnReference(predicate.left, base.alias);
|
|
107
|
+
const rightColumn = baseColumnReference(predicate.right, base.alias);
|
|
108
|
+
const operand = leftColumn !== undefined && rightColumn === undefined
|
|
109
|
+
? templateEquality(predicate.right)
|
|
110
|
+
: rightColumn !== undefined && leftColumn === undefined
|
|
111
|
+
? templateEquality(predicate.left)
|
|
112
|
+
: undefined;
|
|
113
|
+
const column = leftColumn ?? rightColumn;
|
|
114
|
+
if (operand === undefined || column === undefined)
|
|
115
|
+
return undefined;
|
|
116
|
+
equalities.push({ column, ...operand });
|
|
117
|
+
}
|
|
118
|
+
return { table: base.table, equalities, select };
|
|
119
|
+
}
|
|
120
|
+
const templates = new WeakMap();
|
|
121
|
+
/** The template for a cached compiled plan, analyzed once per statement. */
|
|
122
|
+
export function cachedPointReadTemplate(plan) {
|
|
123
|
+
const cached = templates.get(plan);
|
|
124
|
+
if (cached !== undefined)
|
|
125
|
+
return cached;
|
|
126
|
+
const template = pointReadTemplate(plan) ?? null;
|
|
127
|
+
templates.set(plan, template);
|
|
128
|
+
return template;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Substitutes this call's parameters into the statement template. Undefined means a parameter
|
|
132
|
+
* carries a value the fast path cannot compare exactly (NULL, a non-finite number, an invalid
|
|
133
|
+
* Date, or a non-storage value), and the ordinary executor must decide what it means.
|
|
134
|
+
*/
|
|
135
|
+
export function resolvePointReadShape(template, params) {
|
|
136
|
+
const equalities = [];
|
|
137
|
+
for (const entry of template.equalities) {
|
|
138
|
+
if ("value" in entry) {
|
|
139
|
+
equalities.push(entry);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const raw = params[entry.parameter];
|
|
143
|
+
if (raw === undefined)
|
|
144
|
+
return undefined;
|
|
145
|
+
const value = equalityValue(raw);
|
|
146
|
+
if (value === undefined)
|
|
147
|
+
return undefined;
|
|
148
|
+
equalities.push({ column: entry.column, value });
|
|
149
|
+
}
|
|
150
|
+
return { table: template.table, equalities, select: template.select };
|
|
151
|
+
}
|
|
152
|
+
const ascendingRuns = new WeakMap();
|
|
153
|
+
/** Whether the array is non-strictly ascending; memoized per immutable decoded array. */
|
|
154
|
+
export function valuesAreAscending(values) {
|
|
155
|
+
const cached = ascendingRuns.get(values);
|
|
156
|
+
if (cached !== undefined)
|
|
157
|
+
return cached;
|
|
158
|
+
let ascending = true;
|
|
159
|
+
for (let index = 1; index < values.length; index += 1) {
|
|
160
|
+
if ((values[index] ?? 0) < (values[index - 1] ?? 0)) {
|
|
161
|
+
ascending = false;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
ascendingRuns.set(values, ascending);
|
|
166
|
+
return ascending;
|
|
167
|
+
}
|
|
168
|
+
/** The [begin, end) run of slots equal to `target` over an ascending array. */
|
|
169
|
+
export function equalRunRange(values, target) {
|
|
170
|
+
let low = 0;
|
|
171
|
+
let high = values.length;
|
|
172
|
+
while (low < high) {
|
|
173
|
+
const middle = (low + high) >>> 1;
|
|
174
|
+
if ((values[middle] ?? 0) < target)
|
|
175
|
+
low = middle + 1;
|
|
176
|
+
else
|
|
177
|
+
high = middle;
|
|
178
|
+
}
|
|
179
|
+
const begin = low;
|
|
180
|
+
high = values.length;
|
|
181
|
+
while (low < high) {
|
|
182
|
+
const middle = (low + high) >>> 1;
|
|
183
|
+
if ((values[middle] ?? 0) <= target)
|
|
184
|
+
low = middle + 1;
|
|
185
|
+
else
|
|
186
|
+
high = middle;
|
|
187
|
+
}
|
|
188
|
+
return { begin, end: low };
|
|
189
|
+
}
|