@minnowdb/core 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -9
- package/dist/engine/client.d.ts +10 -1
- package/dist/engine/client.d.ts.map +1 -1
- package/dist/engine/client.js +30 -5
- package/dist/engine/client.js.map +1 -1
- package/dist/engine/database.d.ts +55 -6
- package/dist/engine/database.d.ts.map +1 -1
- package/dist/engine/database.js +2689 -898
- package/dist/engine/database.js.map +1 -1
- package/dist/engine/live.d.ts +6 -2
- package/dist/engine/live.d.ts.map +1 -1
- package/dist/engine/live.js +35 -11
- package/dist/engine/live.js.map +1 -1
- package/dist/engine/query-cache.d.ts +13 -1
- package/dist/engine/query-cache.d.ts.map +1 -1
- package/dist/engine/query-cache.js +34 -5
- package/dist/engine/query-cache.js.map +1 -1
- package/dist/engine/query.d.ts +13 -1
- package/dist/engine/query.d.ts.map +1 -1
- package/dist/engine/query.js +73 -46
- package/dist/engine/query.js.map +1 -1
- package/dist/engine/result-wire.d.ts +68 -0
- package/dist/engine/result-wire.d.ts.map +1 -0
- package/dist/engine/result-wire.js +264 -0
- package/dist/engine/result-wire.js.map +1 -0
- package/dist/engine/sort-keys.d.ts +50 -12
- package/dist/engine/sort-keys.d.ts.map +1 -1
- package/dist/engine/sort-keys.js +371 -25
- package/dist/engine/sort-keys.js.map +1 -1
- package/dist/engine/vector.d.ts +11 -0
- package/dist/engine/vector.d.ts.map +1 -1
- package/dist/engine/vector.js +99 -203
- package/dist/engine/vector.js.map +1 -1
- package/dist/engine/worker-host.d.ts +17 -1
- package/dist/engine/worker-host.d.ts.map +1 -1
- package/dist/engine/worker-host.js +87 -13
- package/dist/engine/worker-host.js.map +1 -1
- package/dist/storage/index.d.ts +13 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +13 -0
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/indexeddb.d.ts +5 -1
- package/dist/storage/indexeddb.d.ts.map +1 -1
- package/dist/storage/indexeddb.js +484 -402
- package/dist/storage/indexeddb.js.map +1 -1
- package/dist/storage/memory.d.ts +24 -21
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +124 -1199
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/opfs/files.d.ts +64 -0
- package/dist/storage/opfs/files.d.ts.map +1 -0
- package/dist/storage/opfs/files.js +243 -0
- package/dist/storage/opfs/files.js.map +1 -0
- package/dist/storage/opfs/index.d.ts +3 -0
- package/dist/storage/opfs/index.d.ts.map +1 -0
- package/dist/storage/opfs/index.js +3 -0
- package/dist/storage/opfs/index.js.map +1 -0
- package/dist/storage/opfs/leader.d.ts +130 -0
- package/dist/storage/opfs/leader.d.ts.map +1 -0
- package/dist/storage/opfs/leader.js +1104 -0
- package/dist/storage/opfs/leader.js.map +1 -0
- package/dist/storage/opfs/rpc.d.ts +70 -0
- package/dist/storage/opfs/rpc.d.ts.map +1 -0
- package/dist/storage/opfs/rpc.js +58 -0
- package/dist/storage/opfs/rpc.js.map +1 -0
- package/dist/storage/opfs/store.d.ts +154 -0
- package/dist/storage/opfs/store.d.ts.map +1 -0
- package/dist/storage/opfs/store.js +958 -0
- package/dist/storage/opfs/store.js.map +1 -0
- package/dist/storage/toolkit/extents.d.ts +69 -0
- package/dist/storage/toolkit/extents.d.ts.map +1 -0
- package/dist/storage/toolkit/extents.js +175 -0
- package/dist/storage/toolkit/extents.js.map +1 -0
- package/dist/storage/toolkit/index.d.ts +30 -0
- package/dist/storage/toolkit/index.d.ts.map +1 -0
- package/dist/storage/toolkit/index.js +5 -0
- package/dist/storage/toolkit/index.js.map +1 -0
- package/dist/storage/toolkit/record-core.d.ts +252 -0
- package/dist/storage/toolkit/record-core.d.ts.map +1 -0
- package/dist/storage/toolkit/record-core.js +1670 -0
- package/dist/storage/toolkit/record-core.js.map +1 -0
- package/dist/storage/toolkit/sync-file.d.ts +29 -0
- package/dist/storage/toolkit/sync-file.d.ts.map +1 -0
- package/dist/storage/toolkit/sync-file.js +2 -0
- package/dist/storage/toolkit/sync-file.js.map +1 -0
- package/dist/storage/toolkit/wal.d.ts +22 -0
- package/dist/storage/toolkit/wal.d.ts.map +1 -0
- package/dist/storage/toolkit/wal.js +79 -0
- package/dist/storage/toolkit/wal.js.map +1 -0
- package/dist/storage/toolkit/wire.d.ts +25 -0
- package/dist/storage/toolkit/wire.d.ts.map +1 -0
- package/dist/storage/toolkit/wire.js +100 -0
- package/dist/storage/toolkit/wire.js.map +1 -0
- package/dist/storage/types.d.ts +343 -59
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +112 -11
- package/dist/storage/types.js.map +1 -1
- package/dist/testing/block-store-conformance.d.ts +51 -0
- package/dist/testing/block-store-conformance.d.ts.map +1 -0
- package/dist/testing/block-store-conformance.js +745 -0
- package/dist/testing/block-store-conformance.js.map +1 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/opfs-shim.d.ts +35 -0
- package/dist/testing/opfs-shim.d.ts.map +1 -0
- package/dist/testing/opfs-shim.js +295 -0
- package/dist/testing/opfs-shim.js.map +1 -0
- package/dist/transactions/index.d.ts +42 -1
- package/dist/transactions/index.d.ts.map +1 -1
- package/dist/transactions/index.js +219 -13
- package/dist/transactions/index.js.map +1 -1
- package/dist/worker-protocol/index.d.ts +2 -2
- package/dist/worker-protocol/index.d.ts.map +1 -1
- package/dist/worker-protocol/index.js +2 -1
- package/dist/worker-protocol/index.js.map +1 -1
- package/package.json +10 -2
- package/sql-feature-matrix.json +8 -0
package/dist/engine/vector.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ByteGroupIndex } from "./group-index.js";
|
|
|
4
4
|
import { ByteJoinIndex } from "./join-index.js";
|
|
5
5
|
import { QueryMemoryBudgetError, QueryMemoryContext, } from "./memory.js";
|
|
6
6
|
import { compareSqlStrings, defineSqlResultProperty } from "./sql-semantics.js";
|
|
7
|
-
import { buildSortKeyColumn } from "./sort-keys.js";
|
|
7
|
+
import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
|
|
8
8
|
const DEFAULT_BATCH_ROWS = 2_048;
|
|
9
9
|
/** Above this, locating each IN member separately costs more than scanning between them. */
|
|
10
10
|
const MAX_SPLIT_LIST_MEMBERS = 64;
|
|
@@ -24,6 +24,21 @@ const PACKED_GROUP_ENTRY_BYTES = 24;
|
|
|
24
24
|
const SPILL_PAGE_MAGIC = 0x5350494c;
|
|
25
25
|
const SPILL_PAGE_HEADER_BYTES = 8;
|
|
26
26
|
const vectorTextEncoder = new TextEncoder();
|
|
27
|
+
/** Pages per batched spill write: bounds the transient encoded copies a batch holds. */
|
|
28
|
+
const SPILL_WRITE_BATCH_PAGES = 8;
|
|
29
|
+
/** Writes already-materialized pages through the batch method when the store offers one. */
|
|
30
|
+
async function writeSpillPages(store, pages) {
|
|
31
|
+
const batched = store.putPages?.bind(store);
|
|
32
|
+
if (batched === undefined) {
|
|
33
|
+
for (const page of pages) {
|
|
34
|
+
await store.putPage(page.ownerId, page.runId, page.pageIndex, page.bytes);
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (let start = 0; start < pages.length; start += SPILL_WRITE_BATCH_PAGES) {
|
|
39
|
+
await batched(pages.slice(start, start + SPILL_WRITE_BATCH_PAGES));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
27
42
|
export function createColumnarTable(name, columns, uniqueKey) {
|
|
28
43
|
const first = columns.values().next().value;
|
|
29
44
|
const rowCount = first?.values.length ?? 0;
|
|
@@ -1156,11 +1171,18 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1156
1171
|
partitionBuffers.set(partition, rows);
|
|
1157
1172
|
}
|
|
1158
1173
|
});
|
|
1174
|
+
const flush = [];
|
|
1159
1175
|
for (const [partition, rows] of partitionBuffers) {
|
|
1160
1176
|
const pageIndex = partitionPages[partition] ?? 0;
|
|
1161
|
-
|
|
1177
|
+
flush.push({
|
|
1178
|
+
ownerId,
|
|
1179
|
+
runId: `partition-${String(partition)}`,
|
|
1180
|
+
pageIndex,
|
|
1181
|
+
bytes: encodeSpillRows(spillColumns, rows),
|
|
1182
|
+
});
|
|
1162
1183
|
partitionPages[partition] = pageIndex + 1;
|
|
1163
1184
|
}
|
|
1185
|
+
await writeSpillPages(store, flush);
|
|
1164
1186
|
}
|
|
1165
1187
|
finally {
|
|
1166
1188
|
batchMemory.close();
|
|
@@ -1206,10 +1228,21 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1206
1228
|
}
|
|
1207
1229
|
const runId = `group-${String(runSequence++)}`;
|
|
1208
1230
|
let outputPage = 0;
|
|
1231
|
+
let pending = [];
|
|
1209
1232
|
for (let start = 0; start < rows.length; start += pageRows) {
|
|
1210
|
-
|
|
1233
|
+
pending.push({
|
|
1234
|
+
ownerId,
|
|
1235
|
+
runId,
|
|
1236
|
+
pageIndex: outputPage,
|
|
1237
|
+
bytes: encodeSpillRows(columns, rows.slice(start, start + pageRows)),
|
|
1238
|
+
});
|
|
1211
1239
|
outputPage += 1;
|
|
1240
|
+
if (pending.length >= SPILL_WRITE_BATCH_PAGES) {
|
|
1241
|
+
await writeSpillPages(store, pending);
|
|
1242
|
+
pending = [];
|
|
1243
|
+
}
|
|
1212
1244
|
}
|
|
1245
|
+
await writeSpillPages(store, pending);
|
|
1213
1246
|
runs.push({ id: runId, pageCount: outputPage });
|
|
1214
1247
|
}
|
|
1215
1248
|
finally {
|
|
@@ -2329,7 +2362,17 @@ function appendJoinedRow(output, outputRow, input, row, buildSource, buildRow) {
|
|
|
2329
2362
|
}
|
|
2330
2363
|
}
|
|
2331
2364
|
}
|
|
2332
|
-
const
|
|
2365
|
+
const RETAINED_ROW_BYTES = 3 * QUERY_REFERENCE_BYTES;
|
|
2366
|
+
/**
|
|
2367
|
+
* A LIMIT that keeps at least this share of the scanned rows is not a bound worth enforcing:
|
|
2368
|
+
* what the bounded sink saves in memory it spends in time, and past the share it is cheaper to
|
|
2369
|
+
* keep every row and sort once. Measured at 200k rows, `ORDER BY region, id` and `ORDER BY
|
|
2370
|
+
* amount`, against an unbounded scan-and-sort that costs the same (30 ms and 21 ms) whatever the
|
|
2371
|
+
* LIMIT: at 5% the bound wins (22 ms and 17 ms), at 10% it loses (39 ms and 31 ms), and at 50%
|
|
2372
|
+
* it loses by three times (97 ms and 72 ms). The crossover sits between those two shares, so
|
|
2373
|
+
* the bound stops at the upper one.
|
|
2374
|
+
*/
|
|
2375
|
+
const BOUNDED_SINK_MAX_SHARE = 0.1;
|
|
2333
2376
|
/**
|
|
2334
2377
|
* Accumulates ungrouped result rows. With ORDER BY and LIMIT the sink keeps only the best
|
|
2335
2378
|
* `limit + offset` rows instead of materializing every scanned row for one full sort, so memory
|
|
@@ -2337,15 +2380,18 @@ const DEFERRED_SELECTION_BYTES = 2 * QUERY_REFERENCE_BYTES;
|
|
|
2337
2380
|
* ties resolve by arrival order, so the retained set is precisely the slice a full stable sort
|
|
2338
2381
|
* would have produced.
|
|
2339
2382
|
*
|
|
2340
|
-
*
|
|
2341
|
-
*
|
|
2342
|
-
*
|
|
2343
|
-
*
|
|
2344
|
-
*
|
|
2345
|
-
*
|
|
2346
|
-
*
|
|
2347
|
-
*
|
|
2348
|
-
*
|
|
2383
|
+
* Candidates are tracked as (keys, arrival) entries in a selection buffer that is compacted by
|
|
2384
|
+
* quickselect once it reaches twice the capacity, so a retained row costs a few comparisons
|
|
2385
|
+
* amortized, and a row that cannot beat the current cut line costs nothing past its keys.
|
|
2386
|
+
* Whether the entry also carries the projected row depends on the scan: when the scan
|
|
2387
|
+
* vectors outlive the scan and the order keys resolve to select expressions, projection waits
|
|
2388
|
+
* for the survivors; a streamed scan, a join, or a wildcard select projects the candidate as it
|
|
2389
|
+
* arrives, since there is nothing to read it back from later.
|
|
2390
|
+
*
|
|
2391
|
+
* The bound is dropped for a page that would keep most of the scan anyway: a single-source
|
|
2392
|
+
* plan whose `limit + offset` reaches BOUNDED_SINK_MAX_SHARE of the scan rows keeps every row
|
|
2393
|
+
* and leaves the ordering to one final sort. A join's output size is not known from its scan, so
|
|
2394
|
+
* a joined plan stays bounded.
|
|
2349
2395
|
*/
|
|
2350
2396
|
class ResultSink {
|
|
2351
2397
|
#plan;
|
|
@@ -2354,7 +2400,6 @@ class ResultSink {
|
|
|
2354
2400
|
#keyExpressions;
|
|
2355
2401
|
#deferred;
|
|
2356
2402
|
#rows = [];
|
|
2357
|
-
#heap = [];
|
|
2358
2403
|
#selection = [];
|
|
2359
2404
|
#keyScratch = [];
|
|
2360
2405
|
#threshold;
|
|
@@ -2367,8 +2412,13 @@ class ResultSink {
|
|
|
2367
2412
|
constructor(plan, memory, stableScan) {
|
|
2368
2413
|
this.#plan = plan;
|
|
2369
2414
|
this.#memory = memory;
|
|
2370
|
-
const
|
|
2371
|
-
|
|
2415
|
+
const capacity = (plan.limit ?? 0) + (plan.offset ?? 0);
|
|
2416
|
+
const scanRows = plan.sourceTables[plan.scanSource]?.rowCount ?? 0;
|
|
2417
|
+
const bounded = !plan.grouped &&
|
|
2418
|
+
plan.orderBy.length > 0 &&
|
|
2419
|
+
plan.limit !== undefined &&
|
|
2420
|
+
(plan.joins.length > 0 || capacity < scanRows * BOUNDED_SINK_MAX_SHARE);
|
|
2421
|
+
this.#capacity = bounded ? capacity : undefined;
|
|
2372
2422
|
let keyExpressions;
|
|
2373
2423
|
if (bounded && !plan.wildcard) {
|
|
2374
2424
|
keyExpressions = [];
|
|
@@ -2428,7 +2478,7 @@ class ResultSink {
|
|
|
2428
2478
|
for (let row = 0; row < batch.length; row += 1) {
|
|
2429
2479
|
const threshold = this.#thresholdFirst;
|
|
2430
2480
|
if (threshold === undefined || threshold === null) {
|
|
2431
|
-
this.#
|
|
2481
|
+
this.#addCandidate(batch, row);
|
|
2432
2482
|
continue;
|
|
2433
2483
|
}
|
|
2434
2484
|
const sourceRow = rows?.[row] ?? -1;
|
|
@@ -2442,16 +2492,10 @@ class ResultSink {
|
|
|
2442
2492
|
}
|
|
2443
2493
|
}
|
|
2444
2494
|
}
|
|
2445
|
-
this.#
|
|
2495
|
+
this.#addCandidate(batch, row);
|
|
2446
2496
|
}
|
|
2447
2497
|
return true;
|
|
2448
2498
|
}
|
|
2449
|
-
#addSlow(batch, row) {
|
|
2450
|
-
if (this.#deferred)
|
|
2451
|
-
this.#addDeferred(batch, row);
|
|
2452
|
-
else
|
|
2453
|
-
this.#addEager(batch, row);
|
|
2454
|
-
}
|
|
2455
2499
|
add(batch, row) {
|
|
2456
2500
|
if (this.#capacity === undefined) {
|
|
2457
2501
|
const resultRow = projectBatchRow(this.#plan, batch, row);
|
|
@@ -2461,21 +2505,17 @@ class ResultSink {
|
|
|
2461
2505
|
}
|
|
2462
2506
|
if (this.#capacity === 0)
|
|
2463
2507
|
return;
|
|
2464
|
-
|
|
2465
|
-
this.#addDeferred(batch, row);
|
|
2466
|
-
return;
|
|
2467
|
-
}
|
|
2468
|
-
this.#addEager(batch, row);
|
|
2508
|
+
this.#addCandidate(batch, row);
|
|
2469
2509
|
}
|
|
2470
2510
|
/** Returns accepted rows in arrival order, ready for the shared stable sort and trim. */
|
|
2471
2511
|
finish() {
|
|
2472
2512
|
if (this.#capacity === undefined)
|
|
2473
2513
|
return this.#rows;
|
|
2474
|
-
if (!this.#deferred) {
|
|
2475
|
-
return this.#heap.sort((left, right) => left.seq - right.seq).map((entry) => entry.row);
|
|
2476
|
-
}
|
|
2477
2514
|
this.#compactSelection();
|
|
2478
2515
|
this.#selection.sort((left, right) => left.seq - right.seq);
|
|
2516
|
+
if (!this.#deferred) {
|
|
2517
|
+
return this.#selection.map((entry) => required(entry.row, "Retained row is missing"));
|
|
2518
|
+
}
|
|
2479
2519
|
const scanIndex = new Int32Array(1);
|
|
2480
2520
|
const rowsBySource = [scanIndex];
|
|
2481
2521
|
const batch = { length: 1, rowsBySource };
|
|
@@ -2488,7 +2528,7 @@ class ResultSink {
|
|
|
2488
2528
|
}
|
|
2489
2529
|
return rows;
|
|
2490
2530
|
}
|
|
2491
|
-
#
|
|
2531
|
+
#addCandidate(batch, row) {
|
|
2492
2532
|
const fast = this.#fastFirstKey;
|
|
2493
2533
|
if (fast !== undefined && this.#thresholdFirst !== undefined && this.#thresholdFirst !== null) {
|
|
2494
2534
|
const value = rawFloat64Value(fast.vector, batch.rowsBySource[fast.source]?.[row] ?? -1);
|
|
@@ -2501,7 +2541,18 @@ class ResultSink {
|
|
|
2501
2541
|
}
|
|
2502
2542
|
}
|
|
2503
2543
|
}
|
|
2504
|
-
|
|
2544
|
+
let projected;
|
|
2545
|
+
if (this.#keyExpressions === undefined) {
|
|
2546
|
+
// The order keys are not select expressions, so the projected row is where they live.
|
|
2547
|
+
projected = projectBatchRow(this.#plan, batch, row);
|
|
2548
|
+
for (let index = 0; index < this.#plan.orderBy.length; index += 1) {
|
|
2549
|
+
const order = required(this.#plan.orderBy[index], "Order term is missing");
|
|
2550
|
+
this.#keyScratch[index] = projected[order.outputName] ?? null;
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
else {
|
|
2554
|
+
this.#evaluateKeys(batch, row);
|
|
2555
|
+
}
|
|
2505
2556
|
const seq = this.#seq;
|
|
2506
2557
|
this.#seq += 1;
|
|
2507
2558
|
// The candidate arrived after every retained row, so an order-key tie keeps the retained
|
|
@@ -2510,14 +2561,21 @@ class ResultSink {
|
|
|
2510
2561
|
this.#compareKeys(this.#keyScratch, this.#threshold.keys) >= 0) {
|
|
2511
2562
|
return;
|
|
2512
2563
|
}
|
|
2513
|
-
|
|
2564
|
+
if (!this.#deferred && projected === undefined) {
|
|
2565
|
+
projected = projectBatchRow(this.#plan, batch, row);
|
|
2566
|
+
}
|
|
2567
|
+
let bytes = RETAINED_ROW_BYTES;
|
|
2514
2568
|
for (let index = 0; index < this.#plan.orderBy.length; index += 1) {
|
|
2515
2569
|
bytes = safeMemorySum(bytes, queryValuePayloadBytes(this.#keyScratch[index] ?? null), "Top-N selection entry");
|
|
2516
2570
|
}
|
|
2571
|
+
if (projected !== undefined) {
|
|
2572
|
+
bytes = safeMemorySum(bytes, queryRowPayloadBytes(projected), "Top-N selection entry");
|
|
2573
|
+
}
|
|
2517
2574
|
this.#selection.push({
|
|
2518
2575
|
keys: this.#keyScratch.slice(0, this.#plan.orderBy.length),
|
|
2519
2576
|
seq,
|
|
2520
|
-
sourceRow: batch.rowsBySource[this.#plan.scanSource]?.[row] ?? -1,
|
|
2577
|
+
sourceRow: this.#deferred ? (batch.rowsBySource[this.#plan.scanSource]?.[row] ?? -1) : -1,
|
|
2578
|
+
row: projected,
|
|
2521
2579
|
bytes,
|
|
2522
2580
|
});
|
|
2523
2581
|
this.#reserveSelectionBytes(bytes);
|
|
@@ -2623,64 +2681,6 @@ class ResultSink {
|
|
|
2623
2681
|
low = right + 1;
|
|
2624
2682
|
}
|
|
2625
2683
|
}
|
|
2626
|
-
#addEager(batch, row) {
|
|
2627
|
-
let projected;
|
|
2628
|
-
if (this.#keyExpressions === undefined) {
|
|
2629
|
-
projected = projectBatchRow(this.#plan, batch, row);
|
|
2630
|
-
for (let index = 0; index < this.#plan.orderBy.length; index += 1) {
|
|
2631
|
-
const order = required(this.#plan.orderBy[index], "Order term is missing");
|
|
2632
|
-
this.#keyScratch[index] = projected[order.outputName] ?? null;
|
|
2633
|
-
}
|
|
2634
|
-
}
|
|
2635
|
-
else {
|
|
2636
|
-
this.#evaluateKeys(batch, row);
|
|
2637
|
-
}
|
|
2638
|
-
const seq = this.#seq;
|
|
2639
|
-
this.#seq += 1;
|
|
2640
|
-
const capacity = this.#capacity ?? 0;
|
|
2641
|
-
if (this.#heap.length >= capacity) {
|
|
2642
|
-
const worst = required(this.#heap[0], "Top-N heap root is missing");
|
|
2643
|
-
// An order-key tie keeps the earlier-arriving retained row.
|
|
2644
|
-
if (this.#compareKeys(this.#keyScratch, worst.keys) >= 0)
|
|
2645
|
-
return;
|
|
2646
|
-
const resultRow = projected ?? projectBatchRow(this.#plan, batch, row);
|
|
2647
|
-
const reservation = this.#memory.reserve(this.#entryPayloadBytes(resultRow), "Top-N result row");
|
|
2648
|
-
worst.reservation.release();
|
|
2649
|
-
this.#heap[0] = {
|
|
2650
|
-
row: resultRow,
|
|
2651
|
-
keys: this.#keyScratch.slice(0, this.#plan.orderBy.length),
|
|
2652
|
-
seq,
|
|
2653
|
-
reservation,
|
|
2654
|
-
};
|
|
2655
|
-
this.#siftDown(0);
|
|
2656
|
-
this.#updateEagerThreshold();
|
|
2657
|
-
return;
|
|
2658
|
-
}
|
|
2659
|
-
const resultRow = projected ?? projectBatchRow(this.#plan, batch, row);
|
|
2660
|
-
const reservation = this.#memory.reserve(this.#entryPayloadBytes(resultRow), "Top-N result row");
|
|
2661
|
-
this.#heap.push({
|
|
2662
|
-
row: resultRow,
|
|
2663
|
-
keys: this.#keyScratch.slice(0, this.#plan.orderBy.length),
|
|
2664
|
-
seq,
|
|
2665
|
-
reservation,
|
|
2666
|
-
});
|
|
2667
|
-
this.#siftUp(this.#heap.length - 1);
|
|
2668
|
-
this.#updateEagerThreshold();
|
|
2669
|
-
}
|
|
2670
|
-
/**
|
|
2671
|
-
* The eager cut line for the unboxed batch loop: once the heap is full, its root's first
|
|
2672
|
-
* order key as a float. Non-numeric keys leave the threshold null, which sends every row
|
|
2673
|
-
* through the full comparison.
|
|
2674
|
-
*/
|
|
2675
|
-
#updateEagerThreshold() {
|
|
2676
|
-
if (this.#fastFirstKey === undefined)
|
|
2677
|
-
return;
|
|
2678
|
-
if (this.#heap.length < (this.#capacity ?? 0))
|
|
2679
|
-
return;
|
|
2680
|
-
const first = this.#heap[0]?.keys[0];
|
|
2681
|
-
this.#thresholdFirst =
|
|
2682
|
-
typeof first === "number" ? first : first instanceof Date ? first.getTime() : null;
|
|
2683
|
-
}
|
|
2684
2684
|
#evaluateKeys(batch, row) {
|
|
2685
2685
|
const expressions = required(this.#keyExpressions, "Order keys are missing");
|
|
2686
2686
|
for (let index = 0; index < expressions.length; index += 1) {
|
|
@@ -2688,13 +2688,6 @@ class ResultSink {
|
|
|
2688
2688
|
this.#keyScratch[index] = asQueryValue(evaluateBatchExpression(this.#plan, expression, batch, row));
|
|
2689
2689
|
}
|
|
2690
2690
|
}
|
|
2691
|
-
#entryPayloadBytes(row) {
|
|
2692
|
-
let bytes = queryRowPayloadBytes(row);
|
|
2693
|
-
for (let index = 0; index < this.#plan.orderBy.length; index += 1) {
|
|
2694
|
-
bytes = safeMemorySum(bytes, queryValuePayloadBytes(this.#keyScratch[index] ?? null), "Top-N order keys");
|
|
2695
|
-
}
|
|
2696
|
-
return bytes;
|
|
2697
|
-
}
|
|
2698
2691
|
#compareKeys(left, right) {
|
|
2699
2692
|
const orderBy = this.#plan.orderBy;
|
|
2700
2693
|
for (let index = 0; index < orderBy.length; index += 1) {
|
|
@@ -2708,51 +2701,6 @@ class ResultSink {
|
|
|
2708
2701
|
}
|
|
2709
2702
|
return 0;
|
|
2710
2703
|
}
|
|
2711
|
-
/** Entry `left` loses to `right` when it sorts later under (keys, arrival). */
|
|
2712
|
-
#isWorse(left, right) {
|
|
2713
|
-
const comparison = this.#compareKeys(left.keys, right.keys);
|
|
2714
|
-
if (comparison !== 0)
|
|
2715
|
-
return comparison > 0;
|
|
2716
|
-
return left.seq > right.seq;
|
|
2717
|
-
}
|
|
2718
|
-
#siftUp(index) {
|
|
2719
|
-
let child = index;
|
|
2720
|
-
while (child > 0) {
|
|
2721
|
-
const parent = (child - 1) >> 1;
|
|
2722
|
-
const childEntry = required(this.#heap[child], "Heap entry is missing");
|
|
2723
|
-
const parentEntry = required(this.#heap[parent], "Heap entry is missing");
|
|
2724
|
-
if (!this.#isWorse(childEntry, parentEntry))
|
|
2725
|
-
break;
|
|
2726
|
-
this.#heap[child] = parentEntry;
|
|
2727
|
-
this.#heap[parent] = childEntry;
|
|
2728
|
-
child = parent;
|
|
2729
|
-
}
|
|
2730
|
-
}
|
|
2731
|
-
#siftDown(index) {
|
|
2732
|
-
let parent = index;
|
|
2733
|
-
for (;;) {
|
|
2734
|
-
let worst = parent;
|
|
2735
|
-
let worstEntry = required(this.#heap[worst], "Heap entry is missing");
|
|
2736
|
-
const left = parent * 2 + 1;
|
|
2737
|
-
const right = left + 1;
|
|
2738
|
-
const leftEntry = this.#heap[left];
|
|
2739
|
-
if (leftEntry !== undefined && this.#isWorse(leftEntry, worstEntry)) {
|
|
2740
|
-
worst = left;
|
|
2741
|
-
worstEntry = leftEntry;
|
|
2742
|
-
}
|
|
2743
|
-
const rightEntry = this.#heap[right];
|
|
2744
|
-
if (rightEntry !== undefined && this.#isWorse(rightEntry, worstEntry)) {
|
|
2745
|
-
worst = right;
|
|
2746
|
-
worstEntry = rightEntry;
|
|
2747
|
-
}
|
|
2748
|
-
if (worst === parent)
|
|
2749
|
-
return;
|
|
2750
|
-
const parentEntry = required(this.#heap[parent], "Heap entry is missing");
|
|
2751
|
-
this.#heap[parent] = worstEntry;
|
|
2752
|
-
this.#heap[worst] = parentEntry;
|
|
2753
|
-
parent = worst;
|
|
2754
|
-
}
|
|
2755
|
-
}
|
|
2756
2704
|
}
|
|
2757
2705
|
/**
|
|
2758
2706
|
* Insertion-ordered group-state store. A single bare string-column GROUP BY resolves each row
|
|
@@ -4065,66 +4013,14 @@ function groupKey(value) {
|
|
|
4065
4013
|
throw new TypeError("Group keys must be SQL scalar values");
|
|
4066
4014
|
}
|
|
4067
4015
|
function stableSortRows(rows, orderBy) {
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
const indexes = new Uint32Array(rows.length);
|
|
4071
|
-
const scratch = new Uint32Array(rows.length);
|
|
4072
|
-
for (let index = 0; index < indexes.length; index += 1)
|
|
4073
|
-
indexes[index] = index;
|
|
4074
|
-
let source = indexes;
|
|
4075
|
-
let target = scratch;
|
|
4076
|
-
// One prepared column per term, extracted once; see sort-keys.ts for why the comparison
|
|
4077
|
-
// rather than the merge is what a sort costs. Direction and explicit NULL placement are
|
|
4078
|
-
// resolved here, outside the comparison, because they never vary row to row.
|
|
4079
|
-
const termCount = orderBy.length;
|
|
4016
|
+
// One prepared column per term, extracted once; see sort-keys.ts for the kernel and for why
|
|
4017
|
+
// the comparison rather than the merge is what a sort costs.
|
|
4080
4018
|
const terms = orderBy.map((order) => ({
|
|
4081
|
-
|
|
4082
|
-
nullPlacement: order.nulls === undefined ? 0 : order.nulls === "first" ? 1 : -1,
|
|
4019
|
+
column: buildSortKeyColumn(rows.length, (index) => required(rows[index], "Ordering row is missing")[order.outputName]),
|
|
4083
4020
|
descending: order.direction === "desc",
|
|
4021
|
+
nulls: order.nulls,
|
|
4084
4022
|
}));
|
|
4085
|
-
const
|
|
4086
|
-
const compareIndexes = (leftIndex, rightIndex) => {
|
|
4087
|
-
for (let term = 0; term < termCount; term += 1) {
|
|
4088
|
-
const column = required(columns[term], "Order keys are missing");
|
|
4089
|
-
const { nullPlacement, descending } = required(terms[term], "Order term is missing");
|
|
4090
|
-
if (nullPlacement !== 0) {
|
|
4091
|
-
// An explicit placement is absolute: direction must not negate it.
|
|
4092
|
-
const leftNull = column.isNull(leftIndex);
|
|
4093
|
-
const rightNull = column.isNull(rightIndex);
|
|
4094
|
-
if (leftNull || rightNull) {
|
|
4095
|
-
if (leftNull && rightNull)
|
|
4096
|
-
continue;
|
|
4097
|
-
return (leftNull ? -1 : 1) * nullPlacement;
|
|
4098
|
-
}
|
|
4099
|
-
}
|
|
4100
|
-
const comparison = column.compare(leftIndex, rightIndex);
|
|
4101
|
-
if (comparison !== 0)
|
|
4102
|
-
return descending ? -comparison : comparison;
|
|
4103
|
-
}
|
|
4104
|
-
return 0;
|
|
4105
|
-
};
|
|
4106
|
-
for (let width = 1; width < rows.length; width *= 2) {
|
|
4107
|
-
for (let start = 0; start < rows.length; start += width * 2) {
|
|
4108
|
-
const middle = Math.min(start + width, rows.length);
|
|
4109
|
-
const end = Math.min(start + width * 2, rows.length);
|
|
4110
|
-
let left = start;
|
|
4111
|
-
let right = middle;
|
|
4112
|
-
for (let output = start; output < end; output += 1) {
|
|
4113
|
-
if (right >= end ||
|
|
4114
|
-
(left < middle && compareIndexes(source[left] ?? 0, source[right] ?? 0) <= 0)) {
|
|
4115
|
-
target[output] = source[left] ?? 0;
|
|
4116
|
-
left += 1;
|
|
4117
|
-
}
|
|
4118
|
-
else {
|
|
4119
|
-
target[output] = source[right] ?? 0;
|
|
4120
|
-
right += 1;
|
|
4121
|
-
}
|
|
4122
|
-
}
|
|
4123
|
-
}
|
|
4124
|
-
[source, target] = [target, source];
|
|
4125
|
-
}
|
|
4126
|
-
if (source !== indexes)
|
|
4127
|
-
indexes.set(source);
|
|
4023
|
+
const indexes = sortKeyIndexes(rows.length, terms);
|
|
4128
4024
|
const visited = new Uint8Array(rows.length);
|
|
4129
4025
|
for (let start = 0; start < rows.length; start += 1) {
|
|
4130
4026
|
if (visited[start] === 1)
|