@minnowdb/core 0.7.10 → 0.9.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/engine/artifact-cache.js +3 -2
- package/dist/engine/buffered-writer.js +12 -2
- package/dist/engine/client.d.ts +9 -0
- package/dist/engine/client.js +112 -20
- package/dist/engine/database.d.ts +5 -3
- package/dist/engine/database.js +2199 -1675
- package/dist/engine/errors.d.ts +21 -2
- package/dist/engine/errors.js +30 -1
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +4 -0
- package/dist/engine/live-accept.js +13 -0
- package/dist/engine/live-aggregate.js +264 -0
- package/dist/engine/live-patch.d.ts +21 -0
- package/dist/engine/live-patch.js +31 -0
- package/dist/engine/live.d.ts +14 -0
- package/dist/engine/live.js +146 -142
- package/dist/engine/optimizer.js +11 -6
- package/dist/engine/query-cache.js +19 -15
- package/dist/engine/query-generations.js +61 -0
- package/dist/engine/query-identity.js +41 -0
- package/dist/engine/query.d.ts +7 -13
- package/dist/engine/query.js +298 -435
- package/dist/engine/result-state.d.ts +7 -0
- package/dist/engine/result-state.js +15 -0
- package/dist/engine/sql-domains.js +121 -0
- package/dist/engine/sql-semantics.js +7 -4
- package/dist/engine/typed-live.js +28 -20
- package/dist/engine/vector.d.ts +4 -0
- package/dist/engine/vector.js +14 -14
- package/dist/engine/windows.d.ts +12 -0
- package/dist/engine/windows.js +387 -0
- package/dist/engine/worker-server.d.ts +1 -1
- package/dist/engine/worker-server.js +28 -15
- package/dist/engine/write-coordinator.js +54 -0
- package/dist/plan/model.d.ts +1 -1
- package/dist/storage/indexeddb.js +134 -89
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +3 -1
- package/dist/storage/opfs/leader.js +20 -9
- package/dist/storage/opfs/rpc.js +3 -1
- package/dist/storage/opfs/store.d.ts +2 -0
- package/dist/storage/opfs/store.js +119 -43
- package/dist/storage/toolkit/record-core.js +2 -1
- package/dist/storage/types.d.ts +14 -0
- package/dist/storage/types.js +21 -0
- package/dist/transactions/index.d.ts +3 -0
- package/dist/transactions/index.js +4 -1
- package/dist/worker-protocol/index.d.ts +1 -1
- package/dist/worker-protocol/index.js +1 -1
- package/package.json +2 -2
- package/postgres-feature-profile.json +6 -1
- package/sql-feature-matrix.json +20 -27
- package/dist/date-value.d.ts +0 -20
- package/dist/engine/artifact-cache.d.ts +0 -29
- package/dist/engine/byte-estimates.d.ts +0 -11
- package/dist/engine/cancellation.d.ts +0 -2
- package/dist/engine/defaults.d.ts +0 -29
- package/dist/engine/group-index.d.ts +0 -33
- package/dist/engine/join-index.d.ts +0 -10
- package/dist/engine/live-equal.d.ts +0 -7
- package/dist/engine/point-read.d.ts +0 -59
- package/dist/engine/query-cache.d.ts +0 -21
- package/dist/engine/result-wire.d.ts +0 -70
- package/dist/engine/sort-keys.d.ts +0 -73
- package/dist/engine/sql-domains.d.ts +0 -93
- package/dist/engine/sql-functions.d.ts +0 -11
- package/dist/engine/sql-json.d.ts +0 -40
- package/dist/engine/sql-semantics.d.ts +0 -66
- package/dist/engine/worker-store-indexeddb.d.ts +0 -2
- package/dist/engine/worker-store-memory.d.ts +0 -2
- package/dist/engine/worker-store-opfs.d.ts +0 -2
- package/dist/engine/write-block-planner.d.ts +0 -19
- package/dist/storage/opfs/leader.d.ts +0 -460
- package/dist/storage/opfs/rpc.d.ts +0 -82
- package/dist/storage/opfs/snapshot-ledger.d.ts +0 -41
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { QueryMemoryContext } from "./memory.js";
|
|
2
|
+
import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
|
|
3
|
+
import { compareSqlValues, defineSqlResultProperty } from "./sql-semantics.js";
|
|
4
|
+
import { exactNumericBinary, exactNumericValue, isExactNumeric } from "./sql-domains.js";
|
|
5
|
+
import { throwIfAborted } from "./cancellation.js";
|
|
6
|
+
class FrameAggregate {
|
|
7
|
+
#width;
|
|
8
|
+
#values;
|
|
9
|
+
#counts;
|
|
10
|
+
#window;
|
|
11
|
+
#exact;
|
|
12
|
+
#prefixEnd = 0;
|
|
13
|
+
#prefixTotal = null;
|
|
14
|
+
#prefixCount = 0;
|
|
15
|
+
constructor(values, window, memory, prefix) {
|
|
16
|
+
this.#window = window;
|
|
17
|
+
this.#exact = values.some(isExactNumeric);
|
|
18
|
+
if (prefix) {
|
|
19
|
+
this.#width = 0;
|
|
20
|
+
this.#values = values;
|
|
21
|
+
this.#counts = new Uint32Array(0);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const width = 2 ** Math.ceil(Math.log2(Math.max(1, values.length)));
|
|
25
|
+
this.#width = width;
|
|
26
|
+
memory.tally(width * 2 * 20, "Window aggregate tree");
|
|
27
|
+
this.#values = new Array(width * 2).fill(null);
|
|
28
|
+
this.#counts = new Uint32Array(width * 2);
|
|
29
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
30
|
+
const value = values[index] ?? null;
|
|
31
|
+
this.#values[width + index] = value;
|
|
32
|
+
this.#counts[width + index] = value === null ? 0 : 1;
|
|
33
|
+
}
|
|
34
|
+
for (let index = width - 1; index > 0; index -= 1) {
|
|
35
|
+
this.#counts[index] = (this.#counts[index * 2] ?? 0) + (this.#counts[index * 2 + 1] ?? 0);
|
|
36
|
+
this.#values[index] = this.#combine(this.#values[index * 2] ?? null, this.#values[index * 2 + 1] ?? null);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
#combine(left, right) {
|
|
40
|
+
if (left === null)
|
|
41
|
+
return right;
|
|
42
|
+
if (right === null)
|
|
43
|
+
return left;
|
|
44
|
+
if (this.#window.name === "COUNT")
|
|
45
|
+
return null;
|
|
46
|
+
if (this.#window.name === "MIN")
|
|
47
|
+
return compareSqlValues(left, right) <= 0 ? left : right;
|
|
48
|
+
if (this.#window.name === "MAX")
|
|
49
|
+
return compareSqlValues(left, right) >= 0 ? left : right;
|
|
50
|
+
if (this.#exact) {
|
|
51
|
+
const total = exactNumericBinary("+", left, right);
|
|
52
|
+
if (total === void 0)
|
|
53
|
+
throw new TypeError("Window SUM requires numeric values");
|
|
54
|
+
return total;
|
|
55
|
+
}
|
|
56
|
+
if (typeof left !== "number" || typeof right !== "number")
|
|
57
|
+
throw new TypeError("Window SUM requires numeric values");
|
|
58
|
+
return left + right;
|
|
59
|
+
}
|
|
60
|
+
prefixValue(end, scale) {
|
|
61
|
+
while (this.#prefixEnd < end) {
|
|
62
|
+
const value = this.#values[this.#prefixEnd] ?? null;
|
|
63
|
+
this.#prefixEnd += 1;
|
|
64
|
+
if (value === null)
|
|
65
|
+
continue;
|
|
66
|
+
this.#prefixCount += 1;
|
|
67
|
+
this.#prefixTotal = this.#combine(this.#prefixTotal, value);
|
|
68
|
+
}
|
|
69
|
+
return this.#finish(this.#prefixTotal, this.#prefixCount, scale);
|
|
70
|
+
}
|
|
71
|
+
value(ranges, scale) {
|
|
72
|
+
let total = null;
|
|
73
|
+
let count = 0;
|
|
74
|
+
for (const [from, to] of ranges) {
|
|
75
|
+
let low = from + this.#width;
|
|
76
|
+
let high = to + this.#width;
|
|
77
|
+
let left = null;
|
|
78
|
+
let right = null;
|
|
79
|
+
while (low < high) {
|
|
80
|
+
if (low % 2 === 1) {
|
|
81
|
+
count += this.#counts[low] ?? 0;
|
|
82
|
+
left = this.#combine(left, this.#values[low] ?? null);
|
|
83
|
+
low += 1;
|
|
84
|
+
}
|
|
85
|
+
if (high % 2 === 1) {
|
|
86
|
+
high -= 1;
|
|
87
|
+
count += this.#counts[high] ?? 0;
|
|
88
|
+
right = this.#combine(this.#values[high] ?? null, right);
|
|
89
|
+
}
|
|
90
|
+
low = Math.floor(low / 2);
|
|
91
|
+
high = Math.floor(high / 2);
|
|
92
|
+
}
|
|
93
|
+
total = this.#combine(total, this.#combine(left, right));
|
|
94
|
+
}
|
|
95
|
+
return this.#finish(total, count, scale);
|
|
96
|
+
}
|
|
97
|
+
#finish(total, count, scale) {
|
|
98
|
+
if (this.#window.name === "COUNT")
|
|
99
|
+
return count;
|
|
100
|
+
if (count === 0)
|
|
101
|
+
return null;
|
|
102
|
+
if (this.#window.name !== "AVG")
|
|
103
|
+
return total;
|
|
104
|
+
if (this.#exact)
|
|
105
|
+
return exactNumericBinary("/", total ?? exactNumericValue(0), count, scale) ?? null;
|
|
106
|
+
if (typeof total !== "number")
|
|
107
|
+
throw new TypeError("Window AVG requires numeric values");
|
|
108
|
+
return total / count;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function frameRanges(partition, window, position) {
|
|
112
|
+
const size = partition.indexes.length;
|
|
113
|
+
const frame = window.frame ?? {
|
|
114
|
+
unit: "range",
|
|
115
|
+
start: { kind: "unbounded-preceding" },
|
|
116
|
+
end: { kind: window.orderAliases.length === 0 ? "unbounded-following" : "current-row" }
|
|
117
|
+
};
|
|
118
|
+
const groupEdge = (group) => group < 0 ? 0 : partition.groupStarts[group] ?? size;
|
|
119
|
+
const bound = (edge, start) => {
|
|
120
|
+
switch (edge.kind) {
|
|
121
|
+
case "unbounded-preceding":
|
|
122
|
+
return 0;
|
|
123
|
+
case "unbounded-following":
|
|
124
|
+
return size;
|
|
125
|
+
case "current-row":
|
|
126
|
+
return frame.unit === "rows" ? position + (start ? 0 : 1) : start ? partition.peerStart[position] ?? 0 : partition.peerEnd[position] ?? size;
|
|
127
|
+
case "preceding":
|
|
128
|
+
case "following": {
|
|
129
|
+
const delta = (edge.offset ?? 0) * (edge.kind === "preceding" ? -1 : 1);
|
|
130
|
+
if (frame.unit === "range") {
|
|
131
|
+
const current = partition.orderValues[position] ?? null;
|
|
132
|
+
if (current === null)
|
|
133
|
+
return start ? partition.peerStart[position] ?? 0 : partition.peerEnd[position] ?? size;
|
|
134
|
+
if (typeof current !== "number" && !isExactNumeric(current))
|
|
135
|
+
throw new TypeError("Offset RANGE frames require numeric ORDER BY values");
|
|
136
|
+
const order = window.orderAliases[0];
|
|
137
|
+
if (window.orderAliases.length !== 1 || order === void 0)
|
|
138
|
+
throw new TypeError("Offset RANGE frames require exactly one ORDER BY expression");
|
|
139
|
+
const descending = order.direction === "desc";
|
|
140
|
+
const distance = descending ? -delta : delta;
|
|
141
|
+
const target = isExactNumeric(current) ? exactNumericBinary("+", current, distance) ?? null : current + distance;
|
|
142
|
+
let low2 = 0;
|
|
143
|
+
let high2 = size;
|
|
144
|
+
while (low2 < high2) {
|
|
145
|
+
const middle = Math.floor((low2 + high2) / 2);
|
|
146
|
+
const value = partition.orderValues[middle] ?? null;
|
|
147
|
+
const comparison = value === null ? (order.nulls ?? (descending ? "first" : "last")) === "first" ? -1 : 1 : compareSqlValues(value, target) * (descending ? -1 : 1);
|
|
148
|
+
if (comparison < 0 || !start && comparison === 0)
|
|
149
|
+
low2 = middle + 1;
|
|
150
|
+
else
|
|
151
|
+
high2 = middle;
|
|
152
|
+
}
|
|
153
|
+
return low2;
|
|
154
|
+
}
|
|
155
|
+
return frame.unit === "groups" ? groupEdge((partition.groupOrdinal[position] ?? 0) + delta + (start ? 0 : 1)) : position + delta + (start ? 0 : 1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const low = Math.max(0, Math.min(size, bound(frame.start, true)));
|
|
160
|
+
const high = Math.max(0, Math.min(size, bound(frame.end, false)));
|
|
161
|
+
if (high <= low)
|
|
162
|
+
return [];
|
|
163
|
+
if (frame.exclude === void 0 || frame.exclude === "no-others")
|
|
164
|
+
return [[low, high]];
|
|
165
|
+
const from = frame.exclude === "current-row" ? position : partition.peerStart[position] ?? position;
|
|
166
|
+
const to = frame.exclude === "current-row" ? position + 1 : partition.peerEnd[position] ?? position + 1;
|
|
167
|
+
const ranges = [];
|
|
168
|
+
if (low < Math.min(high, from))
|
|
169
|
+
ranges.push([low, Math.min(high, from)]);
|
|
170
|
+
if (frame.exclude === "ties" && position >= low && position < high)
|
|
171
|
+
ranges.push([position, position + 1]);
|
|
172
|
+
if (Math.max(low, to) < high)
|
|
173
|
+
ranges.push([Math.max(low, to), high]);
|
|
174
|
+
return ranges;
|
|
175
|
+
}
|
|
176
|
+
function* applyPartition(rows, partition, window, result, memory) {
|
|
177
|
+
const { indexes, peerStart, peerEnd, groupOrdinal } = partition;
|
|
178
|
+
const size = indexes.length;
|
|
179
|
+
memory.tally(size * 8, "Window argument values");
|
|
180
|
+
const values = Array.from(indexes, (index) => window.argumentAlias === void 0 ? 1 : rows[index]?.[window.argumentAlias] ?? null);
|
|
181
|
+
const aggregates = /* @__PURE__ */ new Set(["SUM", "AVG", "COUNT", "MIN", "MAX"]);
|
|
182
|
+
const frame = window.frame;
|
|
183
|
+
const prefix = frame === void 0 || (frame.exclude === void 0 || frame.exclude === "no-others") && frame.start.kind === "unbounded-preceding" && (frame.end.kind === "current-row" || frame.end.kind === "unbounded-following");
|
|
184
|
+
const whole = frame?.end.kind === "unbounded-following" || frame === void 0 && window.orderAliases.length === 0;
|
|
185
|
+
const aggregate = aggregates.has(window.name) ? new FrameAggregate(values, window, memory, prefix) : void 0;
|
|
186
|
+
const domain = result.columnDomains[result.columns.indexOf(window.argumentAlias ?? "")];
|
|
187
|
+
for (let position = 0; position < size; position += 1) {
|
|
188
|
+
if (position % 2048 === 0)
|
|
189
|
+
yield;
|
|
190
|
+
let value;
|
|
191
|
+
switch (window.name) {
|
|
192
|
+
case "ROW_NUMBER":
|
|
193
|
+
value = position + 1;
|
|
194
|
+
break;
|
|
195
|
+
case "RANK":
|
|
196
|
+
value = (peerStart[position] ?? 0) + 1;
|
|
197
|
+
break;
|
|
198
|
+
case "DENSE_RANK":
|
|
199
|
+
value = (groupOrdinal[position] ?? 0) + 1;
|
|
200
|
+
break;
|
|
201
|
+
case "PERCENT_RANK":
|
|
202
|
+
value = size === 1 ? 0 : (peerStart[position] ?? 0) / (size - 1);
|
|
203
|
+
break;
|
|
204
|
+
case "CUME_DIST":
|
|
205
|
+
value = (peerEnd[position] ?? size) / size;
|
|
206
|
+
break;
|
|
207
|
+
case "NTILE": {
|
|
208
|
+
const buckets = window.offset ?? 1;
|
|
209
|
+
const width = Math.floor(size / buckets);
|
|
210
|
+
const extra = size % buckets;
|
|
211
|
+
const larger = (width + 1) * extra;
|
|
212
|
+
value = position < larger ? Math.floor(position / (width + 1)) + 1 : extra + Math.floor((position - larger) / width) + 1;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case "LAG":
|
|
216
|
+
case "LEAD": {
|
|
217
|
+
const target = position + (window.offset ?? 1) * (window.name === "LAG" ? -1 : 1);
|
|
218
|
+
value = target < 0 || target >= size ? window.fallback ?? null : values[target] ?? null;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
default: {
|
|
222
|
+
if (aggregate !== void 0 && prefix) {
|
|
223
|
+
const end = whole ? size : frame?.unit === "rows" ? position + 1 : peerEnd[position] ?? size;
|
|
224
|
+
value = aggregate.prefixValue(end, domain?.kind === "numeric" ? domain.scale : void 0);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
const ranges = frameRanges(partition, window, position);
|
|
228
|
+
if (aggregate !== void 0)
|
|
229
|
+
value = aggregate.value(ranges, domain?.kind === "numeric" ? domain.scale : void 0);
|
|
230
|
+
else {
|
|
231
|
+
let remaining = window.name === "NTH_VALUE" ? window.offset ?? 1 : 1;
|
|
232
|
+
value = null;
|
|
233
|
+
if (window.name === "LAST_VALUE") {
|
|
234
|
+
const last = ranges.at(-1);
|
|
235
|
+
value = last === void 0 ? null : values[last[1] - 1] ?? null;
|
|
236
|
+
} else {
|
|
237
|
+
for (const [from, to] of ranges) {
|
|
238
|
+
if (remaining <= to - from) {
|
|
239
|
+
value = values[from + remaining - 1] ?? null;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
remaining -= to - from;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const row = rows[indexes[position] ?? -1];
|
|
249
|
+
if (row !== void 0) {
|
|
250
|
+
if (window.alias in row)
|
|
251
|
+
defineSqlResultProperty(row, window.alias, value);
|
|
252
|
+
else
|
|
253
|
+
row[window.alias] = value;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function* windowSteps(result, windows, options) {
|
|
258
|
+
const memory = options.memoryContext ?? new QueryMemoryContext();
|
|
259
|
+
const owned = options.memoryContext === void 0;
|
|
260
|
+
const rows = [];
|
|
261
|
+
try {
|
|
262
|
+
memory.tally(result.rows.length * (24 + windows.length * 16 + (options.copyRows === false ? 0 : 48 + result.columns.length * 16)), "Window result rows");
|
|
263
|
+
for (let index = 0; index < result.rows.length; index += 1) {
|
|
264
|
+
if (index % 2048 === 0)
|
|
265
|
+
yield;
|
|
266
|
+
const row = result.rows[index] ?? {};
|
|
267
|
+
rows.push(options.copyRows === false ? row : { ...row });
|
|
268
|
+
}
|
|
269
|
+
const groups = /* @__PURE__ */ new Map();
|
|
270
|
+
for (const window of windows) {
|
|
271
|
+
const key = JSON.stringify([window.partitionAliases, window.orderAliases]);
|
|
272
|
+
const group = groups.get(key);
|
|
273
|
+
if (group === void 0)
|
|
274
|
+
groups.set(key, [window]);
|
|
275
|
+
else
|
|
276
|
+
group.push(window);
|
|
277
|
+
}
|
|
278
|
+
for (const group of groups.values()) {
|
|
279
|
+
const window = group[0];
|
|
280
|
+
if (window === void 0)
|
|
281
|
+
continue;
|
|
282
|
+
const work = memory.createChild();
|
|
283
|
+
try {
|
|
284
|
+
const aliases = [
|
|
285
|
+
...window.partitionAliases,
|
|
286
|
+
...window.orderAliases.map(({ alias }) => alias)
|
|
287
|
+
];
|
|
288
|
+
const peers = group.some((member) => !["ROW_NUMBER", "NTILE", "LAG", "LEAD"].includes(member.name) && (["RANK", "DENSE_RANK", "PERCENT_RANK", "CUME_DIST"].includes(member.name) || member.frame?.unit !== "rows" || member.frame.exclude === "group" || member.frame.exclude === "ties"));
|
|
289
|
+
const range = group.some((member) => member.frame?.unit === "range");
|
|
290
|
+
work.tally(rows.length * (20 + (peers ? 20 : 0) + (range ? 8 : 0) + aliases.length * 40), "Window sort and partition buffers");
|
|
291
|
+
const columns = aliases.map((alias) => buildSortKeyColumn(rows.length, (index) => rows[index]?.[alias] ?? null));
|
|
292
|
+
yield;
|
|
293
|
+
const indexes = sortKeyIndexes(rows.length, columns.map((column, index) => {
|
|
294
|
+
const order = window.orderAliases[index - window.partitionAliases.length];
|
|
295
|
+
return { column, descending: order?.direction === "desc", nulls: order?.nulls };
|
|
296
|
+
}));
|
|
297
|
+
const partitions = columns.slice(0, window.partitionAliases.length);
|
|
298
|
+
const orders = columns.slice(window.partitionAliases.length);
|
|
299
|
+
let start = 0;
|
|
300
|
+
while (start < indexes.length) {
|
|
301
|
+
let end = start + 1;
|
|
302
|
+
while (end < indexes.length && partitions.every((column) => column.compare(indexes[start] ?? 0, indexes[end] ?? 0) === 0)) {
|
|
303
|
+
if (end % 2048 === 0)
|
|
304
|
+
yield;
|
|
305
|
+
end += 1;
|
|
306
|
+
}
|
|
307
|
+
const size = end - start;
|
|
308
|
+
const partition = {
|
|
309
|
+
indexes: indexes.subarray(start, end),
|
|
310
|
+
peerStart: new Int32Array(peers ? size : 0),
|
|
311
|
+
peerEnd: new Int32Array(peers ? size : 0),
|
|
312
|
+
groupOrdinal: new Int32Array(peers ? size : 0),
|
|
313
|
+
groupStarts: [],
|
|
314
|
+
orderValues: range ? Array.from(indexes.subarray(start, end), (index) => rows[index]?.[window.orderAliases[0]?.alias ?? ""] ?? null) : []
|
|
315
|
+
};
|
|
316
|
+
let begin = 0;
|
|
317
|
+
for (let position = 1; peers && position <= size; position += 1) {
|
|
318
|
+
if (position % 2048 === 0)
|
|
319
|
+
yield;
|
|
320
|
+
if (position === size || orders.some((column) => column.compare(indexes[start + begin] ?? 0, indexes[start + position] ?? 0) !== 0)) {
|
|
321
|
+
const ordinal = partition.groupStarts.length;
|
|
322
|
+
partition.groupStarts.push(begin);
|
|
323
|
+
partition.peerStart.fill(begin, begin, position);
|
|
324
|
+
partition.peerEnd.fill(position, begin, position);
|
|
325
|
+
partition.groupOrdinal.fill(ordinal, begin, position);
|
|
326
|
+
begin = position;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
for (const member of group) {
|
|
330
|
+
const frameMemory = work.createChild();
|
|
331
|
+
try {
|
|
332
|
+
yield* applyPartition(rows, partition, member, result, frameMemory);
|
|
333
|
+
} finally {
|
|
334
|
+
frameMemory.close();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
start = end;
|
|
338
|
+
}
|
|
339
|
+
} finally {
|
|
340
|
+
work.close();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
columns: [...result.columns, ...windows.map(({ alias }) => alias)],
|
|
345
|
+
columnDomains: [...result.columnDomains, ...windows.map(() => null)],
|
|
346
|
+
rows
|
|
347
|
+
};
|
|
348
|
+
} finally {
|
|
349
|
+
if (owned)
|
|
350
|
+
memory.close();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function applyWindowFunctions(result, windows, options = {}) {
|
|
354
|
+
const steps = windowSteps(result, windows, options);
|
|
355
|
+
try {
|
|
356
|
+
for (; ; ) {
|
|
357
|
+
throwIfAborted(options.signal);
|
|
358
|
+
const step = steps.next();
|
|
359
|
+
if (step.done)
|
|
360
|
+
return step.value;
|
|
361
|
+
}
|
|
362
|
+
} finally {
|
|
363
|
+
steps.return({ columns: [], columnDomains: [], rows: [] });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async function applyWindowFunctionsAsync(result, windows, options = {}) {
|
|
367
|
+
const steps = windowSteps(result, windows, options);
|
|
368
|
+
let yieldedAt = performance.now();
|
|
369
|
+
try {
|
|
370
|
+
for (; ; ) {
|
|
371
|
+
throwIfAborted(options.signal);
|
|
372
|
+
const step = steps.next();
|
|
373
|
+
if (step.done)
|
|
374
|
+
return step.value;
|
|
375
|
+
if (performance.now() - yieldedAt >= 8) {
|
|
376
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
377
|
+
yieldedAt = performance.now();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
} finally {
|
|
381
|
+
steps.return({ columns: [], columnDomains: [], rows: [] });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
export {
|
|
385
|
+
applyWindowFunctions,
|
|
386
|
+
applyWindowFunctionsAsync
|
|
387
|
+
};
|
|
@@ -26,7 +26,7 @@ export type StoreDescriptor = {
|
|
|
26
26
|
durability?: "relaxed" | "strict";
|
|
27
27
|
};
|
|
28
28
|
/** The cloneable subset of MinnowDatabaseOptions; function-valued seams stay worker-side. */
|
|
29
|
-
export type WireDatabaseOptions = Pick<MinnowDatabaseOptions, "compression" | "targetBlockBytes" | "rowsPerBlock" | "maxCommitRetries" | "spillOwnerLeaseMs" | "transactionOwnerLeaseMs" | "transactionIdleTimeoutMs" | "bufferPoolBytes" | "executionMemoryBudgetBytes" | "autoCollectDebtLimitCommits" | "autoCollect" | "autoCompact">;
|
|
29
|
+
export type WireDatabaseOptions = Pick<MinnowDatabaseOptions, "compression" | "targetBlockBytes" | "rowsPerBlock" | "maxCommitRetries" | "coordinateWrites" | "spillOwnerLeaseMs" | "transactionOwnerLeaseMs" | "transactionIdleTimeoutMs" | "bufferPoolBytes" | "executionMemoryBudgetBytes" | "autoCollectDebtLimitCommits" | "autoCollect" | "autoCompact">;
|
|
30
30
|
export interface DatabaseInitPayload {
|
|
31
31
|
store: StoreDescriptor;
|
|
32
32
|
options?: WireDatabaseOptions;
|
|
@@ -455,7 +455,9 @@ class DatabaseRpcServer {
|
|
|
455
455
|
resolveVersion = resolve;
|
|
456
456
|
rejectVersion = reject;
|
|
457
457
|
});
|
|
458
|
+
let snapshotSession;
|
|
458
459
|
const done = this.database.snapshot(async (session) => {
|
|
460
|
+
snapshotSession = session;
|
|
459
461
|
resolveVersion(session.version);
|
|
460
462
|
await new Promise((resolveRelease) => {
|
|
461
463
|
release = resolveRelease;
|
|
@@ -466,7 +468,7 @@ class DatabaseRpcServer {
|
|
|
466
468
|
const version = await versionReady;
|
|
467
469
|
if (release === void 0)
|
|
468
470
|
throw new Error("Snapshot scope did not install its release");
|
|
469
|
-
this.#publishHandle(handleId, { type: "snapshot", release, done });
|
|
471
|
+
this.#publishHandle(handleId, { type: "snapshot", session: snapshotSession, release, done });
|
|
470
472
|
return { handleId, version };
|
|
471
473
|
} catch (error) {
|
|
472
474
|
release?.();
|
|
@@ -494,6 +496,9 @@ class DatabaseRpcServer {
|
|
|
494
496
|
}
|
|
495
497
|
switch (handle.type) {
|
|
496
498
|
case "snapshot": {
|
|
499
|
+
if (method === "query") {
|
|
500
|
+
return this.#querySession(handle.session, args, context);
|
|
501
|
+
}
|
|
497
502
|
if (method !== "close")
|
|
498
503
|
throw new Error(`Unsupported snapshot method: ${method}`);
|
|
499
504
|
handle.release();
|
|
@@ -537,19 +542,21 @@ class DatabaseRpcServer {
|
|
|
537
542
|
}
|
|
538
543
|
}
|
|
539
544
|
}
|
|
545
|
+
async #querySession(session, args, context) {
|
|
546
|
+
const [sql, options, reportStats = false] = args;
|
|
547
|
+
return new ColumnarResult(encodeQueryResult(await session.query(sql, {
|
|
548
|
+
...options,
|
|
549
|
+
signal: context.signal,
|
|
550
|
+
...reportStats ? {
|
|
551
|
+
onStats: (stats) => {
|
|
552
|
+
this.scope.postMessage(rpcEvent(context.requestId, "stats", stats));
|
|
553
|
+
}
|
|
554
|
+
} : {}
|
|
555
|
+
})));
|
|
556
|
+
}
|
|
540
557
|
async #callWriteHandle(handleId, handle, method, args, context) {
|
|
541
|
-
if (method === "query")
|
|
542
|
-
|
|
543
|
-
return new ColumnarResult(encodeQueryResult(await handle.session.query(sql, {
|
|
544
|
-
...options,
|
|
545
|
-
signal: context.signal,
|
|
546
|
-
...reportStats ? {
|
|
547
|
-
onStats: (stats) => {
|
|
548
|
-
this.scope.postMessage(rpcEvent(context.requestId, "stats", stats));
|
|
549
|
-
}
|
|
550
|
-
} : {}
|
|
551
|
-
})));
|
|
552
|
-
}
|
|
558
|
+
if (method === "query")
|
|
559
|
+
return this.#querySession(handle.session, args, context);
|
|
553
560
|
if (method === "execute") {
|
|
554
561
|
const [sql, params] = args;
|
|
555
562
|
return handle.session.execute(sql, params);
|
|
@@ -665,15 +672,21 @@ class DatabaseRpcServer {
|
|
|
665
672
|
case "subscribe": {
|
|
666
673
|
const subscriptionId = this.#claimHandleId(args[0]);
|
|
667
674
|
const query = args[1];
|
|
675
|
+
const patches = args[2]?.patches === true;
|
|
668
676
|
let subscription;
|
|
669
677
|
try {
|
|
670
678
|
subscription = await handle.set.subscribe(query, {
|
|
671
679
|
onChange: (result, delivery) => {
|
|
672
|
-
const
|
|
680
|
+
const patch = patches && !delivery.initial && delivery.retained !== void 0;
|
|
681
|
+
const encoded = encodeQueryResult(patch ? {
|
|
682
|
+
columns: result.columns,
|
|
683
|
+
columnDomains: result.columnDomains,
|
|
684
|
+
rows: result.rows.filter((_, index) => (delivery.retained?.[index] ?? -1) < 0)
|
|
685
|
+
} : result);
|
|
673
686
|
const retained = delivery.retained?.slice();
|
|
674
687
|
if (retained !== void 0)
|
|
675
688
|
encoded.transfer.push(retained.buffer);
|
|
676
|
-
this.scope.postMessage(rpcEvent(subscriptionId, "change", {
|
|
689
|
+
this.scope.postMessage(rpcEvent(subscriptionId, patch ? "patch" : "change", {
|
|
677
690
|
result: encoded.payload,
|
|
678
691
|
delivery: retained === void 0 ? delivery : { ...delivery, retained }
|
|
679
692
|
}), { transfer: encoded.transfer });
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const anonymous = /* @__PURE__ */ new WeakMap();
|
|
2
|
+
const named = /* @__PURE__ */ new Map();
|
|
3
|
+
async function coordinateWrite(store, run, signal) {
|
|
4
|
+
signal.throwIfAborted();
|
|
5
|
+
const name = store.liveQueryChannelName;
|
|
6
|
+
let queue = name === void 0 ? anonymous.get(store) : named.get(name);
|
|
7
|
+
if (queue === void 0) {
|
|
8
|
+
queue = { tail: Promise.resolve() };
|
|
9
|
+
if (name === void 0)
|
|
10
|
+
anonymous.set(store, queue);
|
|
11
|
+
else
|
|
12
|
+
named.set(name, queue);
|
|
13
|
+
}
|
|
14
|
+
const locks = typeof navigator === "undefined" ? void 0 : navigator.locks;
|
|
15
|
+
const lockController = new AbortController();
|
|
16
|
+
let admitted = false;
|
|
17
|
+
const enter = () => {
|
|
18
|
+
signal.throwIfAborted();
|
|
19
|
+
admitted = true;
|
|
20
|
+
return run();
|
|
21
|
+
};
|
|
22
|
+
const operation = queue.tail.then(async () => {
|
|
23
|
+
signal.throwIfAborted();
|
|
24
|
+
return name !== void 0 && locks !== void 0 ? await locks.request(`minnowdb-write:${name}`, { signal: lockController.signal }, enter) : await enter();
|
|
25
|
+
});
|
|
26
|
+
const settled = operation.then(() => void 0, () => void 0);
|
|
27
|
+
queue.tail = settled;
|
|
28
|
+
void settled.then(() => {
|
|
29
|
+
if (queue.tail !== settled)
|
|
30
|
+
return;
|
|
31
|
+
if (name === void 0)
|
|
32
|
+
anonymous.delete(store);
|
|
33
|
+
else
|
|
34
|
+
named.delete(name);
|
|
35
|
+
});
|
|
36
|
+
let abort = () => void 0;
|
|
37
|
+
const cancelled = new Promise((_resolve, reject) => {
|
|
38
|
+
abort = () => {
|
|
39
|
+
if (!admitted) {
|
|
40
|
+
lockController.abort(signal.reason);
|
|
41
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error("Write admission cancelled", { cause: signal.reason }));
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
return await Promise.race([operation, cancelled]);
|
|
48
|
+
} finally {
|
|
49
|
+
signal.removeEventListener("abort", abort);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
coordinateWrite
|
|
54
|
+
};
|
package/dist/plan/model.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export type ComparisonOperator = "=" | "!=" | "<>" | ">" | ">=" | "<" | "<=";
|
|
|
17
17
|
export type AggregateName = "COUNT" | "SUM" | "AVG" | "MIN" | "MAX" | "JSON_ARRAYAGG" | "STRING_AGG"
|
|
18
18
|
/** Optimizer-only aggregate that enforces scalar-subquery cardinality. */
|
|
19
19
|
| "MINNOW_SINGLE_VALUE";
|
|
20
|
-
export type ScalarFunctionName = "ROUND" | "COALESCE" | "DATE_TRUNC" | "DATE_ADD" | "UPPER" | "LOWER" | "LENGTH" | "ABS" | "TRIM" | "LTRIM" | "RTRIM" | "SUBSTR" | "REPLACE" | "INSTR" | "NULLIF" | "GREATEST" | "LEAST" | "FLOOR" | "CEIL" | "MOD" | "POWER" | "SQRT" | "EXTRACT" | "CAST" | "OCTET_LENGTH" | "LPAD" | "RPAD" | "OVERLAY" | "CURRENT_DATE" | "CURRENT_TIMESTAMP" | "LOCALTIME" | "GROUPING" | "JSON_VALUE" | "JSON_QUERY" | "JSON_EXISTS" | "JSON_OBJECT" | "JSON_ARRAY" | "TO_JSON" | "IS_JSON" | "ARRAY"
|
|
20
|
+
export type ScalarFunctionName = "ROUND" | "COALESCE" | "DATE_TRUNC" | "DATE_ADD" | "UPPER" | "LOWER" | "LENGTH" | "ABS" | "TRIM" | "LTRIM" | "RTRIM" | "SUBSTR" | "REPLACE" | "INSTR" | "NULLIF" | "GREATEST" | "LEAST" | "FLOOR" | "CEIL" | "MOD" | "POWER" | "SQRT" | "EXTRACT" | "CAST" | "OCTET_LENGTH" | "LPAD" | "RPAD" | "OVERLAY" | "CURRENT_DATE" | "CURRENT_TIMESTAMP" | "LOCALTIME" | "GROUPING" | "JSON_VALUE" | "JSON_QUERY" | "JSON_EXISTS" | "JSON_OBJECT" | "JSON_ARRAY" | "TO_JSON" | "IS_JSON" | "MINNOW_ARRAY_AT" | "MINNOW_ARRAY_FROM_JSON" | "MINNOW_ARRAY_ELEMENT" | "ARRAY"
|
|
21
21
|
/** Parser-produced `->` JSON member/element access returning a JSON value. */
|
|
22
22
|
| "MINNOW_JSON_GET"
|
|
23
23
|
/** Parser-produced `->>` JSON member/element access returning text. */
|