@minnowdb/core 0.2.1 → 0.4.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/README.md +23 -32
- package/dist/block-format/block.d.ts +32 -13
- package/dist/block-format/block.js +179 -55
- package/dist/block-format/checksum.d.ts +2 -1
- package/dist/block-format/checksum.js +5 -2
- package/dist/block-format/codecs.d.ts +7 -2
- package/dist/block-format/codecs.js +53 -12
- package/dist/block-format/column.d.ts +3 -2
- package/dist/block-format/column.js +96 -30
- package/dist/block-format/index.d.ts +2 -2
- package/dist/block-format/index.js +2 -2
- package/dist/block-format/physical.d.ts +8 -2
- package/dist/block-format/physical.js +14 -6
- package/dist/block-format/types.d.ts +7 -3
- package/dist/block-format/types.js +0 -1
- package/dist/block-format/unicode.d.ts +11 -0
- package/dist/block-format/unicode.js +46 -0
- package/dist/date-value.d.ts +15 -0
- package/dist/date-value.js +64 -0
- package/dist/engine/artifact-cache.d.ts +2 -1
- package/dist/engine/artifact-cache.js +5 -1
- package/dist/engine/batch.d.ts +11 -3
- package/dist/engine/batch.js +17 -7
- package/dist/engine/buffered-writer.d.ts +2 -1
- package/dist/engine/buffered-writer.js +34 -9
- package/dist/engine/cache-limits.d.ts +25 -0
- package/dist/engine/cache-limits.js +25 -0
- package/dist/engine/catalog.d.ts +24 -5
- package/dist/engine/catalog.js +34 -7
- package/dist/engine/client.d.ts +29 -7
- package/dist/engine/client.js +216 -40
- package/dist/engine/database.d.ts +224 -39
- package/dist/engine/database.js +7475 -1539
- package/dist/engine/defaults.d.ts +4 -8
- package/dist/engine/defaults.js +46 -22
- package/dist/engine/errors.d.ts +19 -1
- package/dist/engine/errors.js +32 -2
- package/dist/engine/fts.d.ts +0 -6
- package/dist/engine/fts.js +41 -14
- package/dist/engine/group-index.d.ts +0 -1
- package/dist/engine/group-index.js +6 -11
- package/dist/engine/index.d.ts +16 -10
- package/dist/engine/index.js +12 -9
- package/dist/engine/join-index.d.ts +0 -1
- package/dist/engine/join-index.js +2 -2
- package/dist/engine/keyed-live.d.ts +57 -0
- package/dist/engine/keyed-live.js +226 -0
- package/dist/engine/live-api.d.ts +4 -0
- package/dist/engine/live-api.js +4 -0
- package/dist/engine/live.d.ts +43 -26
- package/dist/engine/live.js +504 -126
- package/dist/engine/memory.d.ts +2 -1
- package/dist/engine/memory.js +3 -2
- package/dist/engine/optimizer.d.ts +0 -1
- package/dist/engine/optimizer.js +262 -27
- package/dist/engine/query-api.d.ts +3 -0
- package/dist/engine/query-api.js +3 -0
- package/dist/engine/query-cache.d.ts +1 -2
- package/dist/engine/query-cache.js +7 -5
- package/dist/engine/query.d.ts +134 -29
- package/dist/engine/query.js +1668 -372
- package/dist/engine/result-wire.d.ts +0 -1
- package/dist/engine/result-wire.js +2 -2
- package/dist/engine/schema-wire.d.ts +6 -3
- package/dist/engine/schema-wire.js +40 -34
- package/dist/engine/schema.d.ts +156 -80
- package/dist/engine/schema.js +570 -108
- package/dist/engine/sort-keys.d.ts +4 -4
- package/dist/engine/sort-keys.js +44 -29
- package/dist/engine/sql-domains.d.ts +31 -0
- package/dist/engine/sql-domains.js +585 -0
- package/dist/engine/sql-driver.d.ts +19 -0
- package/dist/engine/sql-driver.js +1 -0
- package/dist/engine/sql-json.d.ts +7 -9
- package/dist/engine/sql-json.js +75 -15
- package/dist/engine/sql-semantics.d.ts +8 -3
- package/dist/engine/sql-semantics.js +458 -30
- package/dist/engine/typed-live.d.ts +67 -0
- package/dist/engine/typed-live.js +349 -0
- package/dist/engine/vector.d.ts +12 -2
- package/dist/engine/vector.js +635 -119
- package/dist/engine/worker-host.d.ts +9 -2
- package/dist/engine/worker-host.js +620 -152
- package/dist/engine/worker.d.ts +0 -1
- package/dist/engine/worker.js +3 -3
- package/dist/engine/write-block-planner.d.ts +18 -0
- package/dist/engine/write-block-planner.js +112 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/plan/index.d.ts +3 -4
- package/dist/plan/index.js +3 -4
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/storage/indexeddb.d.ts +99 -51
- package/dist/storage/indexeddb.js +13448 -2660
- package/dist/storage/memory.d.ts +64 -57
- package/dist/storage/memory.js +1042 -128
- package/dist/storage/opfs/files.d.ts +18 -7
- package/dist/storage/opfs/files.js +115 -28
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +1 -1
- package/dist/storage/opfs/leader.d.ts +409 -75
- package/dist/storage/opfs/leader.js +4621 -650
- package/dist/storage/opfs/rpc.d.ts +15 -3
- package/dist/storage/opfs/rpc.js +224 -2
- package/dist/storage/opfs/snapshot-ledger.d.ts +40 -0
- package/dist/storage/opfs/snapshot-ledger.js +281 -0
- package/dist/storage/opfs/store.d.ts +33 -99
- package/dist/storage/opfs/store.js +453 -364
- package/dist/storage/persistence.d.ts +37 -0
- package/dist/storage/persistence.js +78 -0
- package/dist/storage/snapshot-stream.d.ts +24 -0
- package/dist/storage/snapshot-stream.js +897 -0
- package/dist/storage/snapshot.d.ts +9 -85
- package/dist/storage/snapshot.js +33 -265
- package/dist/storage/toolkit/extents.d.ts +41 -7
- package/dist/storage/toolkit/extents.js +402 -39
- package/dist/storage/toolkit/index.d.ts +4 -5
- package/dist/storage/toolkit/index.js +28 -4
- package/dist/storage/toolkit/record-core.d.ts +182 -55
- package/dist/storage/toolkit/record-core.js +6158 -1331
- package/dist/storage/toolkit/sync-file.d.ts +10 -1
- package/dist/storage/toolkit/sync-file.js +50 -2
- package/dist/storage/toolkit/wal.d.ts +23 -5
- package/dist/storage/toolkit/wal.js +89 -27
- package/dist/storage/toolkit/wire.d.ts +15 -2
- package/dist/storage/toolkit/wire.js +199 -10
- package/dist/storage/types.d.ts +1439 -204
- package/dist/storage/types.js +1745 -141
- package/dist/testing/block-store-conformance.d.ts +0 -1
- package/dist/testing/block-store-conformance.js +1009 -119
- package/dist/testing/index.d.ts +46 -26
- package/dist/testing/index.js +112 -57
- package/dist/testing/opfs-shim.d.ts +13 -3
- package/dist/testing/opfs-shim.js +40 -6
- package/dist/testing/simulator.d.ts +97 -0
- package/dist/testing/simulator.js +591 -0
- package/dist/testing/sqllogictest.d.ts +89 -0
- package/dist/testing/sqllogictest.js +434 -0
- package/dist/transactions/index.d.ts +92 -16
- package/dist/transactions/index.js +1074 -236
- package/dist/worker-protocol/index.d.ts +3 -2
- package/dist/worker-protocol/index.js +2 -5
- package/package.json +53 -3
- package/postgres-feature-profile.json +223 -0
- package/sql-feature-matrix.json +172 -252
- package/dist/block-format/block.d.ts.map +0 -1
- package/dist/block-format/block.js.map +0 -1
- package/dist/block-format/checksum.d.ts.map +0 -1
- package/dist/block-format/checksum.js.map +0 -1
- package/dist/block-format/codecs.d.ts.map +0 -1
- package/dist/block-format/codecs.js.map +0 -1
- package/dist/block-format/column.d.ts.map +0 -1
- package/dist/block-format/column.js.map +0 -1
- package/dist/block-format/index.d.ts.map +0 -1
- package/dist/block-format/index.js.map +0 -1
- package/dist/block-format/physical.d.ts.map +0 -1
- package/dist/block-format/physical.js.map +0 -1
- package/dist/block-format/types.d.ts.map +0 -1
- package/dist/block-format/types.js.map +0 -1
- package/dist/engine/artifact-cache.d.ts.map +0 -1
- package/dist/engine/artifact-cache.js.map +0 -1
- package/dist/engine/batch.d.ts.map +0 -1
- package/dist/engine/batch.js.map +0 -1
- package/dist/engine/buffered-writer.d.ts.map +0 -1
- package/dist/engine/buffered-writer.js.map +0 -1
- package/dist/engine/catalog.d.ts.map +0 -1
- package/dist/engine/catalog.js.map +0 -1
- package/dist/engine/client.d.ts.map +0 -1
- package/dist/engine/client.js.map +0 -1
- package/dist/engine/coordinator.d.ts +0 -17
- package/dist/engine/coordinator.d.ts.map +0 -1
- package/dist/engine/coordinator.js +0 -60
- package/dist/engine/coordinator.js.map +0 -1
- package/dist/engine/database.d.ts.map +0 -1
- package/dist/engine/database.js.map +0 -1
- package/dist/engine/defaults.d.ts.map +0 -1
- package/dist/engine/defaults.js.map +0 -1
- package/dist/engine/errors.d.ts.map +0 -1
- package/dist/engine/errors.js.map +0 -1
- package/dist/engine/fts.d.ts.map +0 -1
- package/dist/engine/fts.js.map +0 -1
- package/dist/engine/group-index.d.ts.map +0 -1
- package/dist/engine/group-index.js.map +0 -1
- package/dist/engine/index.d.ts.map +0 -1
- package/dist/engine/index.js.map +0 -1
- package/dist/engine/join-index.d.ts.map +0 -1
- package/dist/engine/join-index.js.map +0 -1
- package/dist/engine/live.d.ts.map +0 -1
- package/dist/engine/live.js.map +0 -1
- package/dist/engine/memory.d.ts.map +0 -1
- package/dist/engine/memory.js.map +0 -1
- package/dist/engine/optimizer.d.ts.map +0 -1
- package/dist/engine/optimizer.js.map +0 -1
- package/dist/engine/query-cache.d.ts.map +0 -1
- package/dist/engine/query-cache.js.map +0 -1
- package/dist/engine/query.d.ts.map +0 -1
- package/dist/engine/query.js.map +0 -1
- package/dist/engine/result-wire.d.ts.map +0 -1
- package/dist/engine/result-wire.js.map +0 -1
- package/dist/engine/schema-wire.d.ts.map +0 -1
- package/dist/engine/schema-wire.js.map +0 -1
- package/dist/engine/schema.d.ts.map +0 -1
- package/dist/engine/schema.js.map +0 -1
- package/dist/engine/sort-keys.d.ts.map +0 -1
- package/dist/engine/sort-keys.js.map +0 -1
- package/dist/engine/sql-json.d.ts.map +0 -1
- package/dist/engine/sql-json.js.map +0 -1
- package/dist/engine/sql-semantics.d.ts.map +0 -1
- package/dist/engine/sql-semantics.js.map +0 -1
- package/dist/engine/vector.d.ts.map +0 -1
- package/dist/engine/vector.js.map +0 -1
- package/dist/engine/worker-host.d.ts.map +0 -1
- package/dist/engine/worker-host.js.map +0 -1
- package/dist/engine/worker.d.ts.map +0 -1
- package/dist/engine/worker.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/plan/index.d.ts.map +0 -1
- package/dist/plan/index.js.map +0 -1
- package/dist/storage/fixture-shape.d.ts +0 -42
- package/dist/storage/fixture-shape.d.ts.map +0 -1
- package/dist/storage/fixture-shape.js +0 -146
- package/dist/storage/fixture-shape.js.map +0 -1
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/indexeddb.d.ts.map +0 -1
- package/dist/storage/indexeddb.js.map +0 -1
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js.map +0 -1
- package/dist/storage/opfs/files.d.ts.map +0 -1
- package/dist/storage/opfs/files.js.map +0 -1
- package/dist/storage/opfs/index.d.ts.map +0 -1
- package/dist/storage/opfs/index.js.map +0 -1
- package/dist/storage/opfs/leader.d.ts.map +0 -1
- package/dist/storage/opfs/leader.js.map +0 -1
- package/dist/storage/opfs/rpc.d.ts.map +0 -1
- package/dist/storage/opfs/rpc.js.map +0 -1
- package/dist/storage/opfs/store.d.ts.map +0 -1
- package/dist/storage/opfs/store.js.map +0 -1
- package/dist/storage/snapshot.d.ts.map +0 -1
- package/dist/storage/snapshot.js.map +0 -1
- package/dist/storage/toolkit/extents.d.ts.map +0 -1
- package/dist/storage/toolkit/extents.js.map +0 -1
- package/dist/storage/toolkit/index.d.ts.map +0 -1
- package/dist/storage/toolkit/index.js.map +0 -1
- package/dist/storage/toolkit/record-core.d.ts.map +0 -1
- package/dist/storage/toolkit/record-core.js.map +0 -1
- package/dist/storage/toolkit/sync-file.d.ts.map +0 -1
- package/dist/storage/toolkit/sync-file.js.map +0 -1
- package/dist/storage/toolkit/wal.d.ts.map +0 -1
- package/dist/storage/toolkit/wal.js.map +0 -1
- package/dist/storage/toolkit/wire.d.ts.map +0 -1
- package/dist/storage/toolkit/wire.js.map +0 -1
- package/dist/storage/types.d.ts.map +0 -1
- package/dist/storage/types.js.map +0 -1
- package/dist/testing/block-store-conformance.d.ts.map +0 -1
- package/dist/testing/block-store-conformance.js.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js.map +0 -1
- package/dist/testing/opfs-shim.d.ts.map +0 -1
- package/dist/testing/opfs-shim.js.map +0 -1
- package/dist/testing/seeds.d.ts +0 -11
- package/dist/testing/seeds.d.ts.map +0 -1
- package/dist/testing/seeds.js +0 -50
- package/dist/testing/seeds.js.map +0 -1
- package/dist/transactions/index.d.ts.map +0 -1
- package/dist/transactions/index.js.map +0 -1
- package/dist/worker-protocol/index.d.ts.map +0 -1
- package/dist/worker-protocol/index.js.map +0 -1
package/dist/engine/vector.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dateMilliseconds } from "../date-value.js";
|
|
2
|
+
import { MAX_TEMP_RUN_BATCH_BYTES, MAX_TEMP_RUN_PAGE_BYTES, MAX_TEMP_RUN_PAGES_PER_BATCH, } from "../storage/types.js";
|
|
3
|
+
import { cachedListMembership, childExpressions, distinctFromComparison, nullOrder, isScalarFunctionName, likeMatches, orderOutputName, parseQuantified, quantifiedComparison, scalarFunctionValue, } from "./query.js";
|
|
4
|
+
import { jsonValueOf } from "./sql-json.js";
|
|
2
5
|
import { bm25DocumentScore, cachedQueryTerms, FtsStatsAccumulator, fullTermsMask, renderDocumentValue, termFrequencies, termsMask, tokenize, } from "./fts.js";
|
|
3
6
|
import { ByteGroupIndex } from "./group-index.js";
|
|
4
7
|
import { ByteJoinIndex } from "./join-index.js";
|
|
5
8
|
import { QueryMemoryBudgetError, QueryMemoryContext, } from "./memory.js";
|
|
6
|
-
import { compareSqlStrings, defineSqlResultProperty } from "./sql-semantics.js";
|
|
9
|
+
import { compareSqlStrings, compileSimilarPattern, defineSqlResultProperty, } from "./sql-semantics.js";
|
|
10
|
+
import { exactNumericBinary, exactNumericCompare, collatedDomainCompare, enumDomainCompare, externalSqlDomainValue, isExactNumeric, protectedSqlTextValue, } from "./sql-domains.js";
|
|
7
11
|
import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
|
|
8
12
|
const DEFAULT_BATCH_ROWS = 2_048;
|
|
9
13
|
/** Above this, locating each IN member separately costs more than scanning between them. */
|
|
@@ -25,7 +29,17 @@ const SPILL_PAGE_MAGIC = 0x5350494c;
|
|
|
25
29
|
const SPILL_PAGE_HEADER_BYTES = 8;
|
|
26
30
|
const vectorTextEncoder = new TextEncoder();
|
|
27
31
|
/** Pages per batched spill write: bounds the transient encoded copies a batch holds. */
|
|
28
|
-
const SPILL_WRITE_BATCH_PAGES = 8;
|
|
32
|
+
const SPILL_WRITE_BATCH_PAGES = Math.min(8, MAX_TEMP_RUN_PAGES_PER_BATCH);
|
|
33
|
+
function boundedSpillPageRows(value) {
|
|
34
|
+
const rows = value ?? DEFAULT_BATCH_ROWS;
|
|
35
|
+
if (!Number.isSafeInteger(rows) || rows <= 0) {
|
|
36
|
+
throw new RangeError("Query spill page rows must be a positive whole number");
|
|
37
|
+
}
|
|
38
|
+
// Page rows are a tuning target, not a request to buffer an unbounded number of rows. Larger
|
|
39
|
+
// pages do not improve the block-store round trip because byte-bounded batches provide that
|
|
40
|
+
// amortization independently.
|
|
41
|
+
return Math.min(rows, DEFAULT_BATCH_ROWS);
|
|
42
|
+
}
|
|
29
43
|
/** Writes already-materialized pages through the batch method when the store offers one. */
|
|
30
44
|
async function writeSpillPages(store, pages) {
|
|
31
45
|
const batched = store.putPages?.bind(store);
|
|
@@ -35,9 +49,43 @@ async function writeSpillPages(store, pages) {
|
|
|
35
49
|
}
|
|
36
50
|
return;
|
|
37
51
|
}
|
|
38
|
-
|
|
39
|
-
|
|
52
|
+
let batch = [];
|
|
53
|
+
let batchBytes = 0;
|
|
54
|
+
for (const page of pages) {
|
|
55
|
+
if (page.bytes.byteLength > MAX_TEMP_RUN_PAGE_BYTES) {
|
|
56
|
+
throw new RangeError("Query spill page exceeds the storage limit");
|
|
57
|
+
}
|
|
58
|
+
if (batch.length > 0 &&
|
|
59
|
+
(batch.length === SPILL_WRITE_BATCH_PAGES ||
|
|
60
|
+
batchBytes + page.bytes.byteLength > MAX_TEMP_RUN_BATCH_BYTES)) {
|
|
61
|
+
await batched(batch);
|
|
62
|
+
batch = [];
|
|
63
|
+
batchBytes = 0;
|
|
64
|
+
}
|
|
65
|
+
batch.push(page);
|
|
66
|
+
batchBytes += page.bytes.byteLength;
|
|
40
67
|
}
|
|
68
|
+
if (batch.length > 0)
|
|
69
|
+
await batched(batch);
|
|
70
|
+
}
|
|
71
|
+
async function writeSpillRowPages(store, ownerId, runId, startPageIndex, columns, rows, pageRows) {
|
|
72
|
+
let pending = [];
|
|
73
|
+
let pendingBytes = 0;
|
|
74
|
+
let pageCount = 0;
|
|
75
|
+
for (const bytes of encodeSpillRowPages(columns, rows, pageRows)) {
|
|
76
|
+
if (pending.length > 0 &&
|
|
77
|
+
(pending.length === SPILL_WRITE_BATCH_PAGES ||
|
|
78
|
+
pendingBytes + bytes.byteLength > MAX_TEMP_RUN_BATCH_BYTES)) {
|
|
79
|
+
await writeSpillPages(store, pending);
|
|
80
|
+
pending = [];
|
|
81
|
+
pendingBytes = 0;
|
|
82
|
+
}
|
|
83
|
+
pending.push({ ownerId, runId, pageIndex: startPageIndex + pageCount, bytes });
|
|
84
|
+
pendingBytes += bytes.byteLength;
|
|
85
|
+
pageCount += 1;
|
|
86
|
+
}
|
|
87
|
+
await writeSpillPages(store, pending);
|
|
88
|
+
return pageCount;
|
|
41
89
|
}
|
|
42
90
|
export function createColumnarTable(name, columns, uniqueKey) {
|
|
43
91
|
const first = columns.values().next().value;
|
|
@@ -59,7 +107,7 @@ export function createColumnarTable(name, columns, uniqueKey) {
|
|
|
59
107
|
...(uniqueKey === undefined ? {} : { uniqueKey }),
|
|
60
108
|
};
|
|
61
109
|
}
|
|
62
|
-
export function columnarTableFromRows(name, rows, projectedColumnNames) {
|
|
110
|
+
export function columnarTableFromRows(name, rows, projectedColumnNames, protectText = true) {
|
|
63
111
|
const columnNameSet = new Set(projectedColumnNames);
|
|
64
112
|
if (projectedColumnNames === undefined) {
|
|
65
113
|
for (const row of rows) {
|
|
@@ -72,7 +120,10 @@ export function columnarTableFromRows(name, rows, projectedColumnNames) {
|
|
|
72
120
|
return { name, rowCount: rows.length, columns: new Map() };
|
|
73
121
|
const columns = new Map();
|
|
74
122
|
for (const columnName of columnNames) {
|
|
75
|
-
const values = rows.map((row) =>
|
|
123
|
+
const values = rows.map((row) => {
|
|
124
|
+
const value = row[columnName] ?? null;
|
|
125
|
+
return protectText && typeof value === "string" ? protectedSqlTextValue(value) : value;
|
|
126
|
+
});
|
|
76
127
|
columns.set(columnName, { type: inferVectorType(values), values });
|
|
77
128
|
}
|
|
78
129
|
return createColumnarTable(name, columns);
|
|
@@ -105,7 +156,7 @@ export function prepareVectorQuery(plan, inputTables, options = {}) {
|
|
|
105
156
|
async executeAsync(executionOptions = {}) {
|
|
106
157
|
if (closed)
|
|
107
158
|
throw new Error("Prepared vector query is closed");
|
|
108
|
-
const canSpillSort = bound.orderBy.length > 0 && !bound.grouped;
|
|
159
|
+
const canSpillSort = bound.orderBy.length > 0 && !bound.grouped && !bound.sourceOrdered;
|
|
109
160
|
// An unordered grouped plan spills too: the empty ordering makes the pairwise merge a
|
|
110
161
|
// stable concatenation, and partition-wise accumulation bounds peak group state.
|
|
111
162
|
const canSpillHash = bound.grouped && bound.groupBy.length > 0;
|
|
@@ -130,6 +181,23 @@ export function prepareVectorQuery(plan, inputTables, options = {}) {
|
|
|
130
181
|
executionMemory.close();
|
|
131
182
|
}
|
|
132
183
|
},
|
|
184
|
+
async executeBatches(executionOptions, consume) {
|
|
185
|
+
if (closed)
|
|
186
|
+
throw new Error("Prepared vector query is closed");
|
|
187
|
+
if (bound.grouped || bound.orderBy.length > 0 || bound.joins.length > 0) {
|
|
188
|
+
throw new TypeError("Native query batching requires an ungrouped, unordered, single-source plan");
|
|
189
|
+
}
|
|
190
|
+
if (!Number.isSafeInteger(executionOptions.batchRows) || executionOptions.batchRows <= 0) {
|
|
191
|
+
throw new RangeError("Query batch rows must be a positive whole number");
|
|
192
|
+
}
|
|
193
|
+
const executionMemory = retainedMemory.createChild();
|
|
194
|
+
try {
|
|
195
|
+
return await executeBoundPlanBatches(bound, executionMemory, executionOptions, consume);
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
executionMemory.close();
|
|
199
|
+
}
|
|
200
|
+
},
|
|
133
201
|
close() {
|
|
134
202
|
if (closed)
|
|
135
203
|
return;
|
|
@@ -193,7 +261,7 @@ function createVector(input) {
|
|
|
193
261
|
continue;
|
|
194
262
|
const numericValue = input.type === "datetime"
|
|
195
263
|
? value instanceof Date
|
|
196
|
-
? value
|
|
264
|
+
? dateMilliseconds(value)
|
|
197
265
|
: Number.NaN
|
|
198
266
|
: typeof value === "number"
|
|
199
267
|
? value
|
|
@@ -255,7 +323,7 @@ export function vectorValue(vector, rowIndex) {
|
|
|
255
323
|
return code === NULL_STRING_CODE ? null : (vector.dictionary[code] ?? null);
|
|
256
324
|
}
|
|
257
325
|
function columnarTablePayloadBytes(table) {
|
|
258
|
-
let total = 0;
|
|
326
|
+
let total = table.scanOrder?.byteLength ?? 0;
|
|
259
327
|
for (const vector of table.columns.values()) {
|
|
260
328
|
total = safeMemorySum(total, vector.validity.byteLength, "Column vector payload");
|
|
261
329
|
if (vector.kind === "string") {
|
|
@@ -315,7 +383,178 @@ function disjunctiveNormalForm(predicate) {
|
|
|
315
383
|
};
|
|
316
384
|
return visit(predicate.left) && branches.length > 1 ? branches : undefined;
|
|
317
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Gives a pure comma/CROSS-join group a connected physical order once schemas can resolve bare
|
|
388
|
+
* column names. SQLLogicTest deliberately permutes FROM lists; preserving that written order can
|
|
389
|
+
* otherwise materialize several disconnected Cartesian products before their WHERE equalities
|
|
390
|
+
* become applicable. The streamed base stays fixed; explicit/outer joins and wildcard projection
|
|
391
|
+
* keep their complete written order.
|
|
392
|
+
*
|
|
393
|
+
* One equality that connects each new source becomes the inner hash-join key. The remaining
|
|
394
|
+
* conditions stay ordinary predicates, so null and residual-filter semantics do not change.
|
|
395
|
+
*/
|
|
396
|
+
function orderCartesianJoins(plan, tables) {
|
|
397
|
+
if (plan.joins.length < 2 ||
|
|
398
|
+
plan.select.some((item) => item.expression.kind === "wildcard") ||
|
|
399
|
+
plan.joins.some((join) => !isCartesianJoin(join))) {
|
|
400
|
+
return plan;
|
|
401
|
+
}
|
|
402
|
+
const entries = [
|
|
403
|
+
{ source: plan.base, originalIndex: 0 },
|
|
404
|
+
...plan.joins.map((join, index) => ({
|
|
405
|
+
source: tableSourceOf(join),
|
|
406
|
+
originalIndex: index + 1,
|
|
407
|
+
})),
|
|
408
|
+
];
|
|
409
|
+
const sourceTables = entries.map(({ source }) => tables.get(source.table));
|
|
410
|
+
if (sourceTables.some((table) => table === undefined))
|
|
411
|
+
return plan;
|
|
412
|
+
const predicateSources = plan.predicates.map((predicate) => ({
|
|
413
|
+
predicate,
|
|
414
|
+
sources: rawPredicateSources(predicate, entries, sourceTables),
|
|
415
|
+
}));
|
|
416
|
+
// The preparation layer may stream the written base table through a resident window. Keep
|
|
417
|
+
// that source fixed and order only the fully materialized join sides around it.
|
|
418
|
+
const remaining = new Set(entries.slice(1).map(({ originalIndex }) => originalIndex));
|
|
419
|
+
const ordered = [0];
|
|
420
|
+
while (remaining.size > 0) {
|
|
421
|
+
let best;
|
|
422
|
+
let bestScore = Number.NEGATIVE_INFINITY;
|
|
423
|
+
for (const candidate of remaining) {
|
|
424
|
+
const score = cartesianSourceScore(candidate, ordered, predicateSources);
|
|
425
|
+
if (score > bestScore || (score === bestScore && (best === undefined || candidate < best))) {
|
|
426
|
+
best = candidate;
|
|
427
|
+
bestScore = score;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (best === undefined)
|
|
431
|
+
return plan;
|
|
432
|
+
ordered.push(best);
|
|
433
|
+
remaining.delete(best);
|
|
434
|
+
}
|
|
435
|
+
const predicates = [...plan.predicates];
|
|
436
|
+
const available = new Set([ordered[0] ?? 0]);
|
|
437
|
+
const joins = [];
|
|
438
|
+
for (const sourceIndex of ordered.slice(1)) {
|
|
439
|
+
const source = entries[sourceIndex]?.source;
|
|
440
|
+
if (source === undefined)
|
|
441
|
+
return plan;
|
|
442
|
+
const keyIndex = predicates.findIndex((predicate) => predicateJoinsSource(predicate, sourceIndex, available, entries, sourceTables));
|
|
443
|
+
const key = keyIndex < 0 ? undefined : predicates.splice(keyIndex, 1)[0];
|
|
444
|
+
joins.push(key === undefined ? cartesianJoin(source) : keyedInnerJoin(source, key));
|
|
445
|
+
available.add(sourceIndex);
|
|
446
|
+
}
|
|
447
|
+
return { ...plan, base: plan.base, joins, predicates };
|
|
448
|
+
}
|
|
449
|
+
function isCartesianJoin(join) {
|
|
450
|
+
const condition = join.on;
|
|
451
|
+
return (join.kind === "inner" &&
|
|
452
|
+
condition?.kind === "condition" &&
|
|
453
|
+
condition.operator === "=" &&
|
|
454
|
+
condition.left.kind === "literal" &&
|
|
455
|
+
condition.left.value === 1 &&
|
|
456
|
+
condition.right.kind === "literal" &&
|
|
457
|
+
condition.right.value === 1);
|
|
458
|
+
}
|
|
459
|
+
function tableSourceOf(join) {
|
|
460
|
+
const { kind, left, right, on, full, natural, ...source } = join;
|
|
461
|
+
void kind;
|
|
462
|
+
void left;
|
|
463
|
+
void right;
|
|
464
|
+
void on;
|
|
465
|
+
void full;
|
|
466
|
+
void natural;
|
|
467
|
+
return source;
|
|
468
|
+
}
|
|
469
|
+
function cartesianJoin(source) {
|
|
470
|
+
return {
|
|
471
|
+
...source,
|
|
472
|
+
kind: "inner",
|
|
473
|
+
left: { kind: "literal", value: null },
|
|
474
|
+
right: { kind: "literal", value: null },
|
|
475
|
+
on: {
|
|
476
|
+
kind: "condition",
|
|
477
|
+
operator: "=",
|
|
478
|
+
left: { kind: "literal", value: 1 },
|
|
479
|
+
right: { kind: "literal", value: 1 },
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function keyedInnerJoin(source, predicate) {
|
|
484
|
+
return {
|
|
485
|
+
...source,
|
|
486
|
+
kind: "inner",
|
|
487
|
+
left: predicate.left,
|
|
488
|
+
right: predicate.right,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function cartesianSourceScore(candidate, ordered, predicates) {
|
|
492
|
+
const available = new Set(ordered);
|
|
493
|
+
let local = 0;
|
|
494
|
+
let connected = 0;
|
|
495
|
+
let equality = 0;
|
|
496
|
+
for (const entry of predicates) {
|
|
497
|
+
const sources = entry.sources;
|
|
498
|
+
if (sources?.has(candidate) !== true)
|
|
499
|
+
continue;
|
|
500
|
+
if (sources.size === 1) {
|
|
501
|
+
local++;
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if ([...sources].every((source) => source === candidate || available.has(source))) {
|
|
505
|
+
connected++;
|
|
506
|
+
if (entry.predicate.operator === "=")
|
|
507
|
+
equality++;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// A usable equality dominates a general connection, which dominates a local filter; original
|
|
511
|
+
// order breaks ties. The empty-order branch keeps the helper correct if base selection is ever
|
|
512
|
+
// made schema-aware too, though the streaming executor currently fixes that source.
|
|
513
|
+
return ordered.length === 0 ? local : equality * 100 + connected * 10 + local;
|
|
514
|
+
}
|
|
515
|
+
function rawPredicateSources(predicate, entries, tables) {
|
|
516
|
+
const sources = new Set();
|
|
517
|
+
return rawExpressionSources(predicate.left, entries, tables, sources) &&
|
|
518
|
+
rawExpressionSources(predicate.right, entries, tables, sources)
|
|
519
|
+
? sources
|
|
520
|
+
: undefined;
|
|
521
|
+
}
|
|
522
|
+
function rawExpressionSources(expression, entries, tables, into) {
|
|
523
|
+
if (expression.kind === "subquery" || expression.kind === "exists")
|
|
524
|
+
return false;
|
|
525
|
+
if (expression.kind === "column") {
|
|
526
|
+
const parts = expression.reference.split(".");
|
|
527
|
+
let matches;
|
|
528
|
+
if (parts.length === 2) {
|
|
529
|
+
const match = entries.findIndex(({ source }) => source.alias === parts[0]);
|
|
530
|
+
matches = match < 0 || tables[match]?.columns.has(parts[1] ?? "") !== true ? [] : [match];
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
const column = parts[0] ?? "";
|
|
534
|
+
matches = tables.flatMap((table, index) => table?.columns.has(column) === true ? [index] : []);
|
|
535
|
+
}
|
|
536
|
+
if (matches.length !== 1)
|
|
537
|
+
return false;
|
|
538
|
+
into.add(matches[0] ?? -1);
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
return childExpressions(expression).every((child) => rawExpressionSources(child, entries, tables, into));
|
|
542
|
+
}
|
|
543
|
+
function predicateJoinsSource(predicate, candidate, available, entries, tables) {
|
|
544
|
+
if (predicate.operator !== "=")
|
|
545
|
+
return false;
|
|
546
|
+
const left = new Set();
|
|
547
|
+
const right = new Set();
|
|
548
|
+
if (!rawExpressionSources(predicate.left, entries, tables, left) ||
|
|
549
|
+
!rawExpressionSources(predicate.right, entries, tables, right)) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
const candidateOnly = (sources) => sources.size === 1 && sources.has(candidate);
|
|
553
|
+
const availableOnly = (sources) => sources.size > 0 && [...sources].every((source) => available.has(source));
|
|
554
|
+
return ((candidateOnly(left) && availableOnly(right)) || (candidateOnly(right) && availableOnly(left)));
|
|
555
|
+
}
|
|
318
556
|
function bindPlan(plan, tables, memory, ftsStats) {
|
|
557
|
+
plan = orderCartesianJoins(plan, tables);
|
|
319
558
|
const sources = [plan.base, ...plan.joins];
|
|
320
559
|
const sourceTables = sources.map((source) => {
|
|
321
560
|
const table = tables.get(source.table);
|
|
@@ -422,6 +661,7 @@ function bindPlan(plan, tables, memory, ftsStats) {
|
|
|
422
661
|
...(nulls === undefined ? {} : { nulls }),
|
|
423
662
|
}));
|
|
424
663
|
const grouped = groupBy.length > 0 || aggregateSpecs.length > 0;
|
|
664
|
+
const sourceOrdered = sourceTables[0]?.orderedForPlan === true && standardJoins.length === 0 && !grouped;
|
|
425
665
|
// A single bare string-column GROUP BY can group on dictionary codes: identical values share a
|
|
426
666
|
// code within one vector, so a code-indexed slot table replaces per-row hashing. Streamed
|
|
427
667
|
// vectors swap dictionaries per window; the accumulator remaps its slot table by value on
|
|
@@ -440,9 +680,11 @@ function bindPlan(plan, tables, memory, ftsStats) {
|
|
|
440
680
|
groupBy,
|
|
441
681
|
groupIndexBySignature,
|
|
442
682
|
aggregates: aggregateSpecs,
|
|
683
|
+
hasListAggregate: aggregateSpecs.some((aggregate) => aggregate.name === "JSON_ARRAYAGG" || aggregate.name === "STRING_AGG"),
|
|
443
684
|
select,
|
|
444
685
|
orderBy,
|
|
445
686
|
grouped,
|
|
687
|
+
sourceOrdered,
|
|
446
688
|
...(codeGrouping === undefined ? {} : { codeGrouping }),
|
|
447
689
|
wildcard: plan.select[0]?.expression.kind === "wildcard",
|
|
448
690
|
...(plan.limit === undefined ? {} : { limit: plan.limit }),
|
|
@@ -513,7 +755,11 @@ function bindExpression(expression, sources, aggregateSpecs, aggregateIndexes, m
|
|
|
513
755
|
};
|
|
514
756
|
}
|
|
515
757
|
if (expression.kind === "literal" || expression.kind === "wildcard") {
|
|
516
|
-
return
|
|
758
|
+
return expression.kind === "literal" &&
|
|
759
|
+
typeof expression.value === "string" &&
|
|
760
|
+
expression.internalSqlValue !== true
|
|
761
|
+
? { ...expression, value: protectedSqlTextValue(expression.value), signature }
|
|
762
|
+
: { ...expression, signature };
|
|
517
763
|
}
|
|
518
764
|
if (expression.kind === "column") {
|
|
519
765
|
const parts = expression.reference.split(".");
|
|
@@ -599,12 +845,26 @@ function bindExpression(expression, sources, aggregateSpecs, aggregateIndexes, m
|
|
|
599
845
|
argument.vector.kind === "datetime"
|
|
600
846
|
? { source: argument.source, vector: argument.vector }
|
|
601
847
|
: undefined;
|
|
602
|
-
const rawNumber =
|
|
848
|
+
const rawNumber = expression.name !== "JSON_ARRAYAGG" &&
|
|
849
|
+
expression.name !== "STRING_AGG" &&
|
|
850
|
+
argument.kind === "column" &&
|
|
851
|
+
argument.vector.kind === "number"
|
|
603
852
|
? { source: argument.source, vector: argument.vector }
|
|
604
853
|
: undefined;
|
|
605
854
|
aggregateSpecs.push({
|
|
606
855
|
name: expression.name,
|
|
607
856
|
argument,
|
|
857
|
+
...(expression.name === "STRING_AGG"
|
|
858
|
+
? { delimiter: required(arguments_[1], "STRING_AGG delimiter is missing") }
|
|
859
|
+
: {}),
|
|
860
|
+
...(expression.aggregateOrderBy === undefined
|
|
861
|
+
? {}
|
|
862
|
+
: {
|
|
863
|
+
orderBy: expression.aggregateOrderBy.map((order) => ({
|
|
864
|
+
...order,
|
|
865
|
+
expression: bindExpression(order.expression, sources, aggregateSpecs, aggregateIndexes, memory, ftsBySignature, ftsStats),
|
|
866
|
+
})),
|
|
867
|
+
}),
|
|
608
868
|
// The signature this slot was keyed by is the JSON of the compiled call, which carries the
|
|
609
869
|
// flag — so COUNT(x) and COUNT(DISTINCT x) in one select land in separate slots.
|
|
610
870
|
...(expression.distinct === true ? { distinct: true } : {}),
|
|
@@ -674,7 +934,8 @@ function ensureFtsDictionaryCache(expression, columnIndex, vector, scoring) {
|
|
|
674
934
|
const tokenCount = withScores ? new Uint32Array(vector.dictionary.length) : undefined;
|
|
675
935
|
const termTf = withScores ? new Uint32Array(vector.dictionary.length * termCount) : undefined;
|
|
676
936
|
for (let code = 0; code < vector.dictionary.length; code += 1) {
|
|
677
|
-
const
|
|
937
|
+
const rendered = externalSqlDomainValue(vector.dictionary[code] ?? "");
|
|
938
|
+
const tokens = tokenize(typeof rendered === "string" ? rendered : "");
|
|
678
939
|
masks[code] = termsMask(tokens, expression.terms);
|
|
679
940
|
if (tokenCount !== undefined)
|
|
680
941
|
tokenCount[code] = tokens.length;
|
|
@@ -922,8 +1183,10 @@ function runScanBatch(plan, start, length, groups, output, memory) {
|
|
|
922
1183
|
const scan = sourceRows[plan.scanSource];
|
|
923
1184
|
if (scan === undefined)
|
|
924
1185
|
return false;
|
|
925
|
-
|
|
926
|
-
|
|
1186
|
+
const order = plan.sourceTables[plan.scanSource]?.scanOrder;
|
|
1187
|
+
for (let index = 0; index < length; index += 1) {
|
|
1188
|
+
scan[index] = order?.[start + index] ?? start + index;
|
|
1189
|
+
}
|
|
927
1190
|
const batch = { length, rowsBySource: sourceRows, memory: batchMemory };
|
|
928
1191
|
return consumeJoinedBatches(plan, batch, 0, groups, output, memory);
|
|
929
1192
|
}
|
|
@@ -938,7 +1201,7 @@ function executeBoundPlan(plan, memory) {
|
|
|
938
1201
|
const groups = new GroupAccumulator(plan, memory);
|
|
939
1202
|
const output = new ResultSink(plan, memory, true);
|
|
940
1203
|
const scanRows = plan.sourceTables[plan.scanSource]?.rowCount ?? 0;
|
|
941
|
-
const narrowed = ascendingScanRange(plan, 0, scanRows);
|
|
1204
|
+
const narrowed = plan.sourceOrdered ? undefined : ascendingScanRange(plan, 0, scanRows);
|
|
942
1205
|
const ranges = narrowed?.ranges ?? [
|
|
943
1206
|
{ begin: narrowed?.begin ?? 0, end: narrowed?.end ?? scanRows },
|
|
944
1207
|
];
|
|
@@ -972,7 +1235,7 @@ async function executeBoundPlanAsync(plan, memory, options) {
|
|
|
972
1235
|
// window cannot answer instead of stepping through them. `windowEnd` is where the loader
|
|
973
1236
|
// stopped, which is the block boundary — exactly the span the ordering check covers.
|
|
974
1237
|
const windowEnd = typeof residentEnd === "number" && residentEnd > start ? residentEnd : start + length;
|
|
975
|
-
const narrowed = ascendingScanRange(plan, start, windowEnd);
|
|
1238
|
+
const narrowed = plan.sourceOrdered ? undefined : ascendingScanRange(plan, start, windowEnd);
|
|
976
1239
|
if (narrowed === undefined) {
|
|
977
1240
|
if (runScanBatch(plan, start, length, groups, output, memory))
|
|
978
1241
|
break;
|
|
@@ -1002,15 +1265,61 @@ async function executeBoundPlanAsync(plan, memory, options) {
|
|
|
1002
1265
|
const rows = plan.grouped ? finishGroups(plan, groups.values(), memory) : output.finish();
|
|
1003
1266
|
return finishResult(plan, rows, memory);
|
|
1004
1267
|
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Pull-driven scan execution for cursor-safe plans. A result page owns a child memory context
|
|
1270
|
+
* that closes after the consumer settles, so both modeled memory and live row objects are
|
|
1271
|
+
* bounded by one page. OFFSET/LIMIT are applied once across the whole scan, never per page.
|
|
1272
|
+
*/
|
|
1273
|
+
async function executeBoundPlanBatches(plan, memory, options, consume) {
|
|
1274
|
+
const columns = plan.wildcard ? wildcardColumnNames(plan) : plan.select.map((item) => item.alias);
|
|
1275
|
+
const { limit, offset, ...unbounded } = plan;
|
|
1276
|
+
const scanPlan = unbounded;
|
|
1277
|
+
let skipped = 0;
|
|
1278
|
+
let emitted = 0;
|
|
1279
|
+
const scanRows = scanPlan.sourceTables[scanPlan.scanSource]?.rowCount ?? 0;
|
|
1280
|
+
const step = Math.min(DEFAULT_BATCH_ROWS, options.batchRows);
|
|
1281
|
+
for (let start = 0; start < scanRows && (limit === undefined || emitted < limit);) {
|
|
1282
|
+
options.signal?.throwIfAborted();
|
|
1283
|
+
let length = Math.min(step, scanRows - start);
|
|
1284
|
+
const loaded = options.loadScanWindow?.(start, length);
|
|
1285
|
+
const residentEnd = typeof loaded === "number" || loaded === undefined ? loaded : await loaded;
|
|
1286
|
+
options.signal?.throwIfAborted();
|
|
1287
|
+
if (typeof residentEnd === "number" && residentEnd > start) {
|
|
1288
|
+
length = Math.min(length, residentEnd - start);
|
|
1289
|
+
}
|
|
1290
|
+
const pageMemory = memory.createChild();
|
|
1291
|
+
try {
|
|
1292
|
+
const groups = new GroupAccumulator(scanPlan, pageMemory);
|
|
1293
|
+
const output = new ResultSink(scanPlan, pageMemory, options.loadScanWindow === undefined);
|
|
1294
|
+
runScanBatch(scanPlan, start, length, groups, output, pageMemory);
|
|
1295
|
+
let rows = output.finish();
|
|
1296
|
+
const remainingOffset = Math.max(0, (offset ?? 0) - skipped);
|
|
1297
|
+
if (remainingOffset > 0) {
|
|
1298
|
+
const remove = Math.min(remainingOffset, rows.length);
|
|
1299
|
+
rows = rows.slice(remove);
|
|
1300
|
+
skipped += remove;
|
|
1301
|
+
}
|
|
1302
|
+
if (limit !== undefined && rows.length > limit - emitted) {
|
|
1303
|
+
rows = rows.slice(0, limit - emitted);
|
|
1304
|
+
}
|
|
1305
|
+
if (rows.length > 0) {
|
|
1306
|
+
emitted += rows.length;
|
|
1307
|
+
await consume({ columns: [...columns], rows });
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
finally {
|
|
1311
|
+
pageMemory.close();
|
|
1312
|
+
}
|
|
1313
|
+
start += length;
|
|
1314
|
+
}
|
|
1315
|
+
return columns;
|
|
1316
|
+
}
|
|
1005
1317
|
function createSpillOwnerId() {
|
|
1006
1318
|
return `query-${globalThis.crypto.randomUUID()}`;
|
|
1007
1319
|
}
|
|
1008
1320
|
async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
1009
1321
|
const store = required(options.spillStore, "Query spill store is missing");
|
|
1010
|
-
const pageRows = options.spillPageRows
|
|
1011
|
-
if (!Number.isSafeInteger(pageRows) || pageRows <= 0) {
|
|
1012
|
-
throw new RangeError("Query spill page rows must be a positive whole number");
|
|
1013
|
-
}
|
|
1322
|
+
const pageRows = boundedSpillPageRows(options.spillPageRows);
|
|
1014
1323
|
const columns = plan.wildcard ? wildcardColumnNames(plan) : plan.select.map((item) => item.alias);
|
|
1015
1324
|
const ownerId = createSpillOwnerId();
|
|
1016
1325
|
const runs = [];
|
|
@@ -1051,8 +1360,8 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
|
1051
1360
|
ordering.release();
|
|
1052
1361
|
}
|
|
1053
1362
|
const runId = `run-${String(runSequence++)}`;
|
|
1054
|
-
await store
|
|
1055
|
-
runs.push({ id: runId, pageCount
|
|
1363
|
+
const pageCount = await writeSpillRowPages(store, ownerId, runId, 0, columns, rows, pageRows);
|
|
1364
|
+
runs.push({ id: runId, pageCount });
|
|
1056
1365
|
}
|
|
1057
1366
|
finally {
|
|
1058
1367
|
outputMemory.close();
|
|
@@ -1107,10 +1416,7 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
|
1107
1416
|
}
|
|
1108
1417
|
async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
1109
1418
|
const store = required(options.spillStore, "Query spill store is missing");
|
|
1110
|
-
const pageRows = options.spillPageRows
|
|
1111
|
-
if (!Number.isSafeInteger(pageRows) || pageRows <= 0) {
|
|
1112
|
-
throw new RangeError("Query spill page rows must be a positive whole number");
|
|
1113
|
-
}
|
|
1419
|
+
const pageRows = boundedSpillPageRows(options.spillPageRows);
|
|
1114
1420
|
const partitionCount = 64;
|
|
1115
1421
|
const columns = plan.select.map((item) => item.alias);
|
|
1116
1422
|
const groupColumnNames = plan.groupBy.map((_, index) => `g${String(index)}`);
|
|
@@ -1161,8 +1467,18 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1161
1467
|
const raw = spec.argument.kind === "wildcard"
|
|
1162
1468
|
? 1
|
|
1163
1469
|
: evaluateBatchExpression(plan, spec.argument, batch, row);
|
|
1164
|
-
|
|
1165
|
-
|
|
1470
|
+
if (spec.name === "STRING_AGG" && raw !== null && raw !== undefined) {
|
|
1471
|
+
const delimiter = required(spec.delimiter, "STRING_AGG delimiter is missing");
|
|
1472
|
+
spillRow[`a${String(index)}`] = JSON.stringify([
|
|
1473
|
+
raw,
|
|
1474
|
+
evaluateBatchExpression(plan, delimiter, batch, row),
|
|
1475
|
+
(spec.orderBy ?? []).map((order) => encodeSpillValue(asQueryValue(evaluateBatchExpression(plan, order.expression, batch, row)))),
|
|
1476
|
+
]);
|
|
1477
|
+
}
|
|
1478
|
+
else {
|
|
1479
|
+
spillRow[`a${String(index)}`] =
|
|
1480
|
+
raw === null || raw === undefined ? null : asQueryValue(raw);
|
|
1481
|
+
}
|
|
1166
1482
|
}
|
|
1167
1483
|
batchMemory.tally(queryRowPayloadBytes(spillRow), "Hash spill value row");
|
|
1168
1484
|
const partition = hashQueryValues(groupValues) & (partitionCount - 1);
|
|
@@ -1172,15 +1488,26 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1172
1488
|
}
|
|
1173
1489
|
});
|
|
1174
1490
|
const flush = [];
|
|
1491
|
+
let flushBytes = 0;
|
|
1175
1492
|
for (const [partition, rows] of partitionBuffers) {
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1493
|
+
for (const bytes of encodeSpillRowPages(spillColumns, rows, pageRows)) {
|
|
1494
|
+
if (flush.length > 0 &&
|
|
1495
|
+
(flush.length === SPILL_WRITE_BATCH_PAGES ||
|
|
1496
|
+
flushBytes + bytes.byteLength > MAX_TEMP_RUN_BATCH_BYTES)) {
|
|
1497
|
+
await writeSpillPages(store, flush);
|
|
1498
|
+
flush.length = 0;
|
|
1499
|
+
flushBytes = 0;
|
|
1500
|
+
}
|
|
1501
|
+
const pageIndex = partitionPages[partition] ?? 0;
|
|
1502
|
+
flush.push({
|
|
1503
|
+
ownerId,
|
|
1504
|
+
runId: `partition-${String(partition)}`,
|
|
1505
|
+
pageIndex,
|
|
1506
|
+
bytes,
|
|
1507
|
+
});
|
|
1508
|
+
flushBytes += bytes.byteLength;
|
|
1509
|
+
partitionPages[partition] = pageIndex + 1;
|
|
1510
|
+
}
|
|
1184
1511
|
}
|
|
1185
1512
|
await writeSpillPages(store, flush);
|
|
1186
1513
|
}
|
|
@@ -1227,23 +1554,8 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1227
1554
|
}
|
|
1228
1555
|
}
|
|
1229
1556
|
const runId = `group-${String(runSequence++)}`;
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
for (let start = 0; start < rows.length; start += pageRows) {
|
|
1233
|
-
pending.push({
|
|
1234
|
-
ownerId,
|
|
1235
|
-
runId,
|
|
1236
|
-
pageIndex: outputPage,
|
|
1237
|
-
bytes: encodeSpillRows(columns, rows.slice(start, start + pageRows)),
|
|
1238
|
-
});
|
|
1239
|
-
outputPage += 1;
|
|
1240
|
-
if (pending.length >= SPILL_WRITE_BATCH_PAGES) {
|
|
1241
|
-
await writeSpillPages(store, pending);
|
|
1242
|
-
pending = [];
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
await writeSpillPages(store, pending);
|
|
1246
|
-
runs.push({ id: runId, pageCount: outputPage });
|
|
1557
|
+
const pageCount = await writeSpillRowPages(store, ownerId, runId, 0, columns, rows, pageRows);
|
|
1558
|
+
runs.push({ id: runId, pageCount });
|
|
1247
1559
|
}
|
|
1248
1560
|
finally {
|
|
1249
1561
|
partitionMemory.close();
|
|
@@ -1379,11 +1691,10 @@ async function mergeSpillRuns(store, ownerId, left, right, outputId, columns, or
|
|
|
1379
1691
|
const flush = async () => {
|
|
1380
1692
|
if (outputPage.length === 0)
|
|
1381
1693
|
return;
|
|
1382
|
-
await store
|
|
1694
|
+
pageIndex += await writeSpillRowPages(store, ownerId, outputId, pageIndex, columns, outputPage, pageRows);
|
|
1383
1695
|
outputPage = [];
|
|
1384
1696
|
outputMemory.close();
|
|
1385
1697
|
outputMemory = mergeMemory.createChild();
|
|
1386
|
-
pageIndex += 1;
|
|
1387
1698
|
};
|
|
1388
1699
|
try {
|
|
1389
1700
|
let leftRow = await leftReader.next();
|
|
@@ -1446,7 +1757,7 @@ function createSpillRunReader(store, ownerId, run, columns, memory) {
|
|
|
1446
1757
|
}
|
|
1447
1758
|
function compareOrderedRows(left, right, orderBy) {
|
|
1448
1759
|
for (const order of orderBy) {
|
|
1449
|
-
const placed =
|
|
1760
|
+
const placed = nullOrder(left[order.outputName], right[order.outputName], order.nulls, order.direction);
|
|
1450
1761
|
if (placed !== undefined && placed !== 0)
|
|
1451
1762
|
return placed;
|
|
1452
1763
|
const comparison = compareValues(left[order.outputName], right[order.outputName]);
|
|
@@ -1455,19 +1766,63 @@ function compareOrderedRows(left, right, orderBy) {
|
|
|
1455
1766
|
}
|
|
1456
1767
|
return 0;
|
|
1457
1768
|
}
|
|
1458
|
-
function
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1769
|
+
function encodeSpillRow(columns, row) {
|
|
1770
|
+
return {
|
|
1771
|
+
bytes: vectorTextEncoder.encode(JSON.stringify(columns.map((column) => encodeSpillValue(row[column] ?? null)))),
|
|
1772
|
+
modeledBytes: queryRowPayloadBytes(row),
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
function buildSpillPage(rows, byteLength, modeledBytes) {
|
|
1776
|
+
const bytes = new Uint8Array(byteLength);
|
|
1465
1777
|
const header = new DataView(bytes.buffer);
|
|
1466
1778
|
header.setUint32(0, SPILL_PAGE_MAGIC, true);
|
|
1467
1779
|
header.setUint32(4, modeledBytes, true);
|
|
1468
|
-
|
|
1780
|
+
let offset = SPILL_PAGE_HEADER_BYTES;
|
|
1781
|
+
bytes[offset++] = 0x5b;
|
|
1782
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
1783
|
+
if (index > 0)
|
|
1784
|
+
bytes[offset++] = 0x2c;
|
|
1785
|
+
const row = required(rows[index], "Encoded spill row is missing");
|
|
1786
|
+
bytes.set(row.bytes, offset);
|
|
1787
|
+
offset += row.bytes.byteLength;
|
|
1788
|
+
}
|
|
1789
|
+
bytes[offset] = 0x5d;
|
|
1469
1790
|
return bytes;
|
|
1470
1791
|
}
|
|
1792
|
+
/** Encodes pages incrementally and splits on both the requested row target and the hard byte cap. */
|
|
1793
|
+
function* encodeSpillRowPages(columns, rows, pageRows) {
|
|
1794
|
+
let pending = [];
|
|
1795
|
+
let byteLength = SPILL_PAGE_HEADER_BYTES + 2;
|
|
1796
|
+
let modeledBytes = 0;
|
|
1797
|
+
const flush = () => {
|
|
1798
|
+
const page = buildSpillPage(pending, byteLength, modeledBytes);
|
|
1799
|
+
pending = [];
|
|
1800
|
+
byteLength = SPILL_PAGE_HEADER_BYTES + 2;
|
|
1801
|
+
modeledBytes = 0;
|
|
1802
|
+
return page;
|
|
1803
|
+
};
|
|
1804
|
+
for (const row of rows) {
|
|
1805
|
+
const encoded = encodeSpillRow(columns, row);
|
|
1806
|
+
const separatorBytes = pending.length === 0 ? 0 : 1;
|
|
1807
|
+
const nextByteLength = byteLength + separatorBytes + encoded.bytes.byteLength;
|
|
1808
|
+
const nextModeledBytes = modeledBytes + encoded.modeledBytes;
|
|
1809
|
+
if (pending.length > 0 &&
|
|
1810
|
+
(pending.length === pageRows ||
|
|
1811
|
+
nextByteLength > MAX_TEMP_RUN_PAGE_BYTES ||
|
|
1812
|
+
nextModeledBytes > 0xffffffff)) {
|
|
1813
|
+
yield flush();
|
|
1814
|
+
}
|
|
1815
|
+
const addedSeparatorBytes = pending.length === 0 ? 0 : 1;
|
|
1816
|
+
byteLength += addedSeparatorBytes + encoded.bytes.byteLength;
|
|
1817
|
+
modeledBytes += encoded.modeledBytes;
|
|
1818
|
+
if (byteLength > MAX_TEMP_RUN_PAGE_BYTES || modeledBytes > 0xffffffff) {
|
|
1819
|
+
throw new RangeError("One query result row exceeds the spill page storage limit");
|
|
1820
|
+
}
|
|
1821
|
+
pending.push(encoded);
|
|
1822
|
+
}
|
|
1823
|
+
if (pending.length > 0)
|
|
1824
|
+
yield flush();
|
|
1825
|
+
}
|
|
1471
1826
|
function decodeSpillRows(columns, bytes) {
|
|
1472
1827
|
spillRowsModeledBytes(bytes);
|
|
1473
1828
|
const value = JSON.parse(new TextDecoder().decode(bytes.subarray(SPILL_PAGE_HEADER_BYTES)));
|
|
@@ -1498,7 +1853,7 @@ function encodeSpillValue(value) {
|
|
|
1498
1853
|
return [2, Object.is(value, -0) ? "-0" : String(value)];
|
|
1499
1854
|
if (typeof value === "string")
|
|
1500
1855
|
return [3, value];
|
|
1501
|
-
return [4, value
|
|
1856
|
+
return [4, dateMilliseconds(value)];
|
|
1502
1857
|
}
|
|
1503
1858
|
function decodeSpillValue(value) {
|
|
1504
1859
|
if (!Array.isArray(value))
|
|
@@ -1533,7 +1888,7 @@ function executeMetadataCount(plan, memory) {
|
|
|
1533
1888
|
}
|
|
1534
1889
|
function finishResult(plan, inputRows, memory) {
|
|
1535
1890
|
const rows = inputRows;
|
|
1536
|
-
if (plan.orderBy.length > 0) {
|
|
1891
|
+
if (plan.orderBy.length > 0 && !plan.sourceOrdered) {
|
|
1537
1892
|
const ordering = memory.reserve(safeMemoryProduct(rows.length, Uint32Array.BYTES_PER_ELEMENT * 2 +
|
|
1538
1893
|
Uint8Array.BYTES_PER_ELEMENT +
|
|
1539
1894
|
// Per extracted sort key: one slot, plus the null mask a numeric column adds beside
|
|
@@ -2110,7 +2465,7 @@ function consumeJoinedBatches(plan, batch, joinIndex, groups, output, memory, pr
|
|
|
2110
2465
|
let working = batch;
|
|
2111
2466
|
let complete = prefiltered;
|
|
2112
2467
|
let owned;
|
|
2113
|
-
if (
|
|
2468
|
+
if (!prefiltered) {
|
|
2114
2469
|
const filtered = prefilterBatch(plan, batch, join);
|
|
2115
2470
|
if (filtered !== undefined) {
|
|
2116
2471
|
complete = filtered.complete;
|
|
@@ -2144,7 +2499,7 @@ function consumeJoinedBatches(plan, batch, joinIndex, groups, output, memory, pr
|
|
|
2144
2499
|
function reachedEarlyLimit(plan, outputRows) {
|
|
2145
2500
|
// Early termination must still produce the rows the trailing OFFSET will discard.
|
|
2146
2501
|
return (!plan.grouped &&
|
|
2147
|
-
plan.orderBy.length === 0 &&
|
|
2502
|
+
(plan.orderBy.length === 0 || plan.sourceOrdered) &&
|
|
2148
2503
|
plan.limit !== undefined &&
|
|
2149
2504
|
outputRows >= plan.limit + (plan.offset ?? 0));
|
|
2150
2505
|
}
|
|
@@ -2186,11 +2541,18 @@ function* joinBatches(plan, input, join, memory) {
|
|
|
2186
2541
|
const probeKey = evaluateBatchExpression(plan, join.probe, input, row);
|
|
2187
2542
|
let buildRow = probeKey === null ? -1 : join.lookup.firstRow(probeKey);
|
|
2188
2543
|
if (buildRow < 0) {
|
|
2189
|
-
if (join.kind === "left") {
|
|
2544
|
+
if (join.kind === "left" || join.kind === "anti") {
|
|
2190
2545
|
appendJoinedRow(ensureOutput(), outputLength, input, row, join.buildSource, -1);
|
|
2191
2546
|
outputLength += 1;
|
|
2192
2547
|
}
|
|
2193
2548
|
}
|
|
2549
|
+
else if (join.kind === "semi") {
|
|
2550
|
+
appendJoinedRow(ensureOutput(), outputLength, input, row, join.buildSource, buildRow);
|
|
2551
|
+
outputLength += 1;
|
|
2552
|
+
}
|
|
2553
|
+
else if (join.kind === "anti") {
|
|
2554
|
+
// A match excludes the probe row.
|
|
2555
|
+
}
|
|
2194
2556
|
else {
|
|
2195
2557
|
while (buildRow >= 0) {
|
|
2196
2558
|
appendJoinedRow(ensureOutput(), outputLength, input, row, join.buildSource, buildRow);
|
|
@@ -2254,12 +2616,16 @@ function* loopJoinBatches(plan, input, join, loop, memory) {
|
|
|
2254
2616
|
if (!holds)
|
|
2255
2617
|
continue;
|
|
2256
2618
|
matched = true;
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2619
|
+
if (join.kind !== "anti") {
|
|
2620
|
+
appendJoinedRow(ensureOutput(), outputLength, input, row, join.buildSource, buildRow);
|
|
2621
|
+
outputLength += 1;
|
|
2622
|
+
if (outputLength === DEFAULT_BATCH_ROWS)
|
|
2623
|
+
yield* emit();
|
|
2624
|
+
}
|
|
2625
|
+
if (join.kind === "semi" || join.kind === "anti")
|
|
2626
|
+
break;
|
|
2261
2627
|
}
|
|
2262
|
-
if (!matched && join.kind === "left") {
|
|
2628
|
+
if (!matched && (join.kind === "left" || join.kind === "anti")) {
|
|
2263
2629
|
appendJoinedRow(ensureOutput(), outputLength, input, row, join.buildSource, -1);
|
|
2264
2630
|
outputLength += 1;
|
|
2265
2631
|
if (outputLength === DEFAULT_BATCH_ROWS)
|
|
@@ -2309,10 +2675,12 @@ function joinUniqueBatch(plan, input, join, memory) {
|
|
|
2309
2675
|
cache[code] = buildRow;
|
|
2310
2676
|
}
|
|
2311
2677
|
}
|
|
2312
|
-
if (buildRow < 0 && join.kind === "inner")
|
|
2678
|
+
if ((buildRow < 0 && (join.kind === "inner" || join.kind === "semi")) ||
|
|
2679
|
+
(buildRow >= 0 && join.kind === "anti")) {
|
|
2313
2680
|
continue;
|
|
2681
|
+
}
|
|
2314
2682
|
selectedRows[outputLength] = row;
|
|
2315
|
-
buildRows[outputLength] = buildRow;
|
|
2683
|
+
buildRows[outputLength] = join.kind === "anti" ? -1 : buildRow;
|
|
2316
2684
|
outputLength += 1;
|
|
2317
2685
|
}
|
|
2318
2686
|
}
|
|
@@ -2320,10 +2688,12 @@ function joinUniqueBatch(plan, input, join, memory) {
|
|
|
2320
2688
|
for (let row = 0; row < input.length; row += 1) {
|
|
2321
2689
|
const probeKey = evaluateBatchExpression(plan, join.probe, input, row);
|
|
2322
2690
|
const buildRow = probeKey === null ? -1 : join.lookup.firstRow(probeKey);
|
|
2323
|
-
if (buildRow < 0 && join.kind === "inner")
|
|
2691
|
+
if ((buildRow < 0 && (join.kind === "inner" || join.kind === "semi")) ||
|
|
2692
|
+
(buildRow >= 0 && join.kind === "anti")) {
|
|
2324
2693
|
continue;
|
|
2694
|
+
}
|
|
2325
2695
|
selectedRows[outputLength] = row;
|
|
2326
|
-
buildRows[outputLength] = buildRow;
|
|
2696
|
+
buildRows[outputLength] = join.kind === "anti" ? -1 : buildRow;
|
|
2327
2697
|
outputLength += 1;
|
|
2328
2698
|
}
|
|
2329
2699
|
}
|
|
@@ -2415,6 +2785,7 @@ class ResultSink {
|
|
|
2415
2785
|
const capacity = (plan.limit ?? 0) + (plan.offset ?? 0);
|
|
2416
2786
|
const scanRows = plan.sourceTables[plan.scanSource]?.rowCount ?? 0;
|
|
2417
2787
|
const bounded = !plan.grouped &&
|
|
2788
|
+
!plan.sourceOrdered &&
|
|
2418
2789
|
plan.orderBy.length > 0 &&
|
|
2419
2790
|
plan.limit !== undefined &&
|
|
2420
2791
|
(plan.joins.length > 0 || capacity < scanRows * BOUNDED_SINK_MAX_SHARE);
|
|
@@ -2625,7 +2996,7 @@ class ResultSink {
|
|
|
2625
2996
|
: typeof first === "number"
|
|
2626
2997
|
? first
|
|
2627
2998
|
: first instanceof Date
|
|
2628
|
-
? first
|
|
2999
|
+
? dateMilliseconds(first)
|
|
2629
3000
|
: undefined;
|
|
2630
3001
|
}
|
|
2631
3002
|
/**
|
|
@@ -2692,7 +3063,7 @@ class ResultSink {
|
|
|
2692
3063
|
const orderBy = this.#plan.orderBy;
|
|
2693
3064
|
for (let index = 0; index < orderBy.length; index += 1) {
|
|
2694
3065
|
const order = required(orderBy[index], "Order term is missing");
|
|
2695
|
-
const placed =
|
|
3066
|
+
const placed = nullOrder(left[index], right[index], order.nulls, order.direction);
|
|
2696
3067
|
if (placed !== undefined && placed !== 0)
|
|
2697
3068
|
return placed;
|
|
2698
3069
|
const comparison = compareValues(left[index], right[index]);
|
|
@@ -2820,7 +3191,7 @@ class GroupAccumulator {
|
|
|
2820
3191
|
* COUNT/SUM/AVG aggregates: one pass with unboxed reads and no per-row dispatch. Returns false
|
|
2821
3192
|
* when the plan shape needs the generic per-row path.
|
|
2822
3193
|
*/
|
|
2823
|
-
consumeFast(batch
|
|
3194
|
+
consumeFast(batch) {
|
|
2824
3195
|
const plan = this.#plan;
|
|
2825
3196
|
const codeGrouping = plan.codeGrouping;
|
|
2826
3197
|
const globalGroup = plan.groupBy.length === 0;
|
|
@@ -2831,7 +3202,7 @@ class GroupAccumulator {
|
|
|
2831
3202
|
if (specs === undefined)
|
|
2832
3203
|
return false;
|
|
2833
3204
|
// The purest shape — global COUNT(*) with no predicates — needs no row loop at all.
|
|
2834
|
-
if (globalGroup &&
|
|
3205
|
+
if (globalGroup && specs.every((spec) => spec.kind === "star")) {
|
|
2835
3206
|
const state = required(this.#index.getEmpty(), "Grouped query state is missing");
|
|
2836
3207
|
for (let index = 0; index < specs.length; index += 1) {
|
|
2837
3208
|
state.counts[index] = (state.counts[index] ?? 0) + batch.length;
|
|
@@ -2881,8 +3252,6 @@ class GroupAccumulator {
|
|
|
2881
3252
|
slots: groupVector.codes.length,
|
|
2882
3253
|
};
|
|
2883
3254
|
for (let row = 0; row < batch.length; row += 1) {
|
|
2884
|
-
if (hasPredicates && !passes(row))
|
|
2885
|
-
continue;
|
|
2886
3255
|
let state = globalState;
|
|
2887
3256
|
if (state === undefined && grouping !== undefined && states !== undefined) {
|
|
2888
3257
|
const sourceRow = groupRows?.[row] ?? -1;
|
|
@@ -3101,7 +3470,7 @@ function consumeBatch(plan, batch, groups, output, memory, prefiltered = false)
|
|
|
3101
3470
|
}
|
|
3102
3471
|
return;
|
|
3103
3472
|
}
|
|
3104
|
-
if (plan.grouped && groups.consumeFast(batch
|
|
3473
|
+
if (plan.grouped && groups.consumeFast(batch))
|
|
3105
3474
|
return;
|
|
3106
3475
|
if (!plan.grouped && output.tryAddBatch(batch))
|
|
3107
3476
|
return;
|
|
@@ -3133,6 +3502,9 @@ function createGroupState(groupValues, plan, memory) {
|
|
|
3133
3502
|
payloadBytes = safeMemorySum(payloadBytes, queryValuePayloadBytes(value), "Group state");
|
|
3134
3503
|
}
|
|
3135
3504
|
payloadBytes = safeMemorySum(payloadBytes, safeMemoryProduct(plan.aggregates.length, AGGREGATE_ACCUMULATOR_BYTES, "Aggregate accumulator state"), "Group state");
|
|
3505
|
+
if (plan.hasListAggregate) {
|
|
3506
|
+
payloadBytes = safeMemorySum(payloadBytes, safeMemoryProduct(plan.aggregates.length, QUERY_REFERENCE_BYTES, "JSON aggregate list slots"), "Group state");
|
|
3507
|
+
}
|
|
3136
3508
|
// tally, not reserve: a group state lives until the context closes and is never released
|
|
3137
3509
|
// on its own, so a per-group QueryMemoryReservation object — retained in the context's Set
|
|
3138
3510
|
// for the whole query — is pure overhead at one per distinct group.
|
|
@@ -3162,6 +3534,9 @@ function createGroupState(groupValues, plan, memory) {
|
|
|
3162
3534
|
counts: new Float64Array(plan.aggregates.length),
|
|
3163
3535
|
sums: new Float64Array(plan.aggregates.length),
|
|
3164
3536
|
values: new Array(plan.aggregates.length),
|
|
3537
|
+
...(plan.hasListAggregate
|
|
3538
|
+
? { lists: new Array(plan.aggregates.length) }
|
|
3539
|
+
: {}),
|
|
3165
3540
|
valueReservations: new Array(plan.aggregates.length),
|
|
3166
3541
|
valueReservationBytes: new Float64Array(plan.aggregates.length),
|
|
3167
3542
|
distincts,
|
|
@@ -3174,7 +3549,7 @@ function createGroupState(groupValues, plan, memory) {
|
|
|
3174
3549
|
* string a row genuinely holds.
|
|
3175
3550
|
*/
|
|
3176
3551
|
function distinctKey(value) {
|
|
3177
|
-
return value instanceof Date ?
|
|
3552
|
+
return value instanceof Date ? `\0d${String(dateMilliseconds(value))}` : value;
|
|
3178
3553
|
}
|
|
3179
3554
|
/**
|
|
3180
3555
|
* Whether this value is the first of its kind for the slot, folding it into the set when it is.
|
|
@@ -3236,7 +3611,11 @@ function updateAggregates(plan, state, batch, row, memory) {
|
|
|
3236
3611
|
const value = spec.argument.kind === "wildcard"
|
|
3237
3612
|
? 1
|
|
3238
3613
|
: evaluateBatchExpression(plan, spec.argument, batch, row);
|
|
3239
|
-
|
|
3614
|
+
const delimiter = spec.delimiter === undefined
|
|
3615
|
+
? undefined
|
|
3616
|
+
: evaluateBatchExpression(plan, spec.delimiter, batch, row);
|
|
3617
|
+
const orderValues = (spec.orderBy ?? []).map((order) => asQueryValue(evaluateBatchExpression(plan, order.expression, batch, row)));
|
|
3618
|
+
applyAggregateValue(spec, state, index, value, memory, delimiter, orderValues);
|
|
3240
3619
|
}
|
|
3241
3620
|
}
|
|
3242
3621
|
/** Reads a float64 slot (number value or raw epoch milliseconds) unboxed, or null when invalid. */
|
|
@@ -3258,10 +3637,63 @@ function rawFloat64Value(vector, rowIndex) {
|
|
|
3258
3637
|
function updateAggregatesFromValues(plan, state, values, memory) {
|
|
3259
3638
|
for (let index = 0; index < plan.aggregates.length; index += 1) {
|
|
3260
3639
|
const spec = required(plan.aggregates[index], "Aggregate specification is missing");
|
|
3261
|
-
|
|
3640
|
+
const value = values[index];
|
|
3641
|
+
if (spec.name === "STRING_AGG" && typeof value === "string") {
|
|
3642
|
+
const decoded = JSON.parse(value);
|
|
3643
|
+
if (!Array.isArray(decoded) || decoded.length !== 3) {
|
|
3644
|
+
throw new Error("Spilled STRING_AGG input is invalid");
|
|
3645
|
+
}
|
|
3646
|
+
const encodedOrder = decoded[2];
|
|
3647
|
+
if (!Array.isArray(encodedOrder))
|
|
3648
|
+
throw new Error("Spilled STRING_AGG order is invalid");
|
|
3649
|
+
applyAggregateValue(spec, state, index, decoded[0], memory, decoded[1], encodedOrder.map(decodeSpillValue));
|
|
3650
|
+
}
|
|
3651
|
+
else {
|
|
3652
|
+
applyAggregateValue(spec, state, index, value, memory);
|
|
3653
|
+
}
|
|
3262
3654
|
}
|
|
3263
3655
|
}
|
|
3264
|
-
function applyAggregateValue(spec, state, index, value, memory) {
|
|
3656
|
+
function applyAggregateValue(spec, state, index, value, memory, delimiter, orderValues = []) {
|
|
3657
|
+
if (spec.name === "JSON_ARRAYAGG") {
|
|
3658
|
+
const member = asQueryValue(value ?? null);
|
|
3659
|
+
if (spec.distinct === true && !firstOfItsKind(state, index, member, memory))
|
|
3660
|
+
return;
|
|
3661
|
+
state.counts[index] = (state.counts[index] ?? 0) + 1;
|
|
3662
|
+
const lists = required(state.lists, "JSON aggregate list state is missing");
|
|
3663
|
+
let list = lists[index];
|
|
3664
|
+
if (list === undefined) {
|
|
3665
|
+
list = [];
|
|
3666
|
+
lists[index] = list;
|
|
3667
|
+
}
|
|
3668
|
+
list.push(member);
|
|
3669
|
+
memory.tally(safeMemorySum(QUERY_REFERENCE_BYTES, queryValuePayloadBytes(member), "JSON_ARRAYAGG member"), "JSON_ARRAYAGG member");
|
|
3670
|
+
return;
|
|
3671
|
+
}
|
|
3672
|
+
if (spec.name === "STRING_AGG") {
|
|
3673
|
+
if (value === null || value === undefined)
|
|
3674
|
+
return;
|
|
3675
|
+
value = externalSqlDomainValue(value);
|
|
3676
|
+
delimiter = externalSqlDomainValue(delimiter);
|
|
3677
|
+
if (typeof value !== "string")
|
|
3678
|
+
throw new TypeError("STRING_AGG value must be a string");
|
|
3679
|
+
if (delimiter !== null && delimiter !== undefined && typeof delimiter !== "string") {
|
|
3680
|
+
throw new TypeError("STRING_AGG delimiter must be a string");
|
|
3681
|
+
}
|
|
3682
|
+
const distinctMember = JSON.stringify([value, delimiter ?? ""]);
|
|
3683
|
+
if (spec.distinct === true && !firstOfItsKind(state, index, distinctMember, memory))
|
|
3684
|
+
return;
|
|
3685
|
+
const member = JSON.stringify([value, delimiter ?? "", orderValues.map(encodeSpillValue)]);
|
|
3686
|
+
state.counts[index] = (state.counts[index] ?? 0) + 1;
|
|
3687
|
+
const lists = required(state.lists, "STRING_AGG list state is missing");
|
|
3688
|
+
let list = lists[index];
|
|
3689
|
+
if (list === undefined) {
|
|
3690
|
+
list = [];
|
|
3691
|
+
lists[index] = list;
|
|
3692
|
+
}
|
|
3693
|
+
list.push(member);
|
|
3694
|
+
memory.tally(safeMemorySum(QUERY_REFERENCE_BYTES, queryValuePayloadBytes(member), "STRING_AGG member"), "STRING_AGG member");
|
|
3695
|
+
return;
|
|
3696
|
+
}
|
|
3265
3697
|
if (value === null || value === undefined)
|
|
3266
3698
|
return;
|
|
3267
3699
|
// One gate for every path that accumulates: the generic per-row path, the raw datetime path,
|
|
@@ -3270,7 +3702,16 @@ function applyAggregateValue(spec, state, index, value, memory) {
|
|
|
3270
3702
|
return;
|
|
3271
3703
|
state.counts[index] = (state.counts[index] ?? 0) + 1;
|
|
3272
3704
|
if (spec.name === "SUM" || spec.name === "AVG") {
|
|
3273
|
-
|
|
3705
|
+
if (isExactNumeric(value)) {
|
|
3706
|
+
const current = state.values[index];
|
|
3707
|
+
const sum = current === undefined ? value : exactNumericBinary("+", current, value);
|
|
3708
|
+
if (sum === null || sum === undefined)
|
|
3709
|
+
throw new Error("Exact NUMERIC sum disappeared");
|
|
3710
|
+
replaceAggregateValue(state, index, sum, `${spec.name} aggregate value`, memory);
|
|
3711
|
+
}
|
|
3712
|
+
else {
|
|
3713
|
+
state.sums[index] = (state.sums[index] ?? 0) + numeric(value);
|
|
3714
|
+
}
|
|
3274
3715
|
}
|
|
3275
3716
|
else if (spec.name === "MIN" &&
|
|
3276
3717
|
(state.values[index] === undefined || compareValues(value, state.values[index]) < 0)) {
|
|
@@ -3364,12 +3805,55 @@ function evaluateFinalExpression(plan, expression, group) {
|
|
|
3364
3805
|
return count;
|
|
3365
3806
|
if (count === 0)
|
|
3366
3807
|
return null;
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
if (expression.name === "
|
|
3371
|
-
|
|
3808
|
+
if (expression.name === "JSON_ARRAYAGG") {
|
|
3809
|
+
return JSON.stringify((required(group.lists, "JSON aggregate list state is missing")[aggregateIndex] ?? []).map(jsonValueOf));
|
|
3810
|
+
}
|
|
3811
|
+
if (expression.name === "STRING_AGG") {
|
|
3812
|
+
const members = required(group.lists, "STRING_AGG list state is missing")[aggregateIndex] ?? [];
|
|
3813
|
+
const decoded = members.map((encoded) => {
|
|
3814
|
+
if (typeof encoded !== "string")
|
|
3815
|
+
throw new Error("STRING_AGG member is invalid");
|
|
3816
|
+
const pair = JSON.parse(encoded);
|
|
3817
|
+
if (!Array.isArray(pair) ||
|
|
3818
|
+
typeof pair[0] !== "string" ||
|
|
3819
|
+
typeof pair[1] !== "string" ||
|
|
3820
|
+
!Array.isArray(pair[2])) {
|
|
3821
|
+
throw new Error("STRING_AGG member is invalid");
|
|
3822
|
+
}
|
|
3823
|
+
return {
|
|
3824
|
+
value: pair[0],
|
|
3825
|
+
delimiter: pair[1],
|
|
3826
|
+
order: pair[2].map(decodeSpillValue),
|
|
3827
|
+
};
|
|
3828
|
+
});
|
|
3829
|
+
const orderBy = plan.aggregates[aggregateIndex]?.orderBy ?? [];
|
|
3830
|
+
if (orderBy.length > 0) {
|
|
3831
|
+
decoded.sort((left, right) => {
|
|
3832
|
+
for (const [index, order] of orderBy.entries()) {
|
|
3833
|
+
const a = left.order[index];
|
|
3834
|
+
const b = right.order[index];
|
|
3835
|
+
const placed = nullOrder(a, b, order.nulls, order.direction);
|
|
3836
|
+
if (placed !== undefined && placed !== 0)
|
|
3837
|
+
return placed;
|
|
3838
|
+
const compared = compareValues(a, b);
|
|
3839
|
+
if (compared !== 0)
|
|
3840
|
+
return order.direction === "desc" ? -compared : compared;
|
|
3841
|
+
}
|
|
3842
|
+
return 0;
|
|
3843
|
+
});
|
|
3844
|
+
}
|
|
3845
|
+
return protectedSqlTextValue(decoded
|
|
3846
|
+
.map((member, index) => (index === 0 ? member.value : member.delimiter + member.value))
|
|
3847
|
+
.join(""));
|
|
3848
|
+
}
|
|
3372
3849
|
const value = group.values[aggregateIndex] ?? null;
|
|
3850
|
+
if (expression.name === "SUM")
|
|
3851
|
+
return isExactNumeric(value) ? value : (group.sums[aggregateIndex] ?? 0);
|
|
3852
|
+
if (expression.name === "AVG") {
|
|
3853
|
+
if (isExactNumeric(value))
|
|
3854
|
+
return exactNumericBinary("/", value, count);
|
|
3855
|
+
return (group.sums[aggregateIndex] ?? 0) / count;
|
|
3856
|
+
}
|
|
3373
3857
|
// Raw-millisecond datetime extremes re-box into a Date only here, once per surviving group.
|
|
3374
3858
|
if (typeof value === "number" && plan.aggregates[aggregateIndex]?.rawDatetime !== undefined) {
|
|
3375
3859
|
return new Date(value);
|
|
@@ -3460,7 +3944,7 @@ function detectPrimitiveComparison(predicate) {
|
|
|
3460
3944
|
return { source: column.source, vector, operator, value };
|
|
3461
3945
|
}
|
|
3462
3946
|
if (vector.kind === "datetime" && value instanceof Date) {
|
|
3463
|
-
return { source: column.source, vector, operator, value: value
|
|
3947
|
+
return { source: column.source, vector, operator, value: dateMilliseconds(value) };
|
|
3464
3948
|
}
|
|
3465
3949
|
if (vector.kind === "boolean" &&
|
|
3466
3950
|
typeof value === "boolean" &&
|
|
@@ -3508,7 +3992,7 @@ function detectPrimitiveInList(predicate) {
|
|
|
3508
3992
|
else {
|
|
3509
3993
|
if (!(value instanceof Date))
|
|
3510
3994
|
return undefined;
|
|
3511
|
-
members.add(value
|
|
3995
|
+
members.add(dateMilliseconds(value));
|
|
3512
3996
|
}
|
|
3513
3997
|
}
|
|
3514
3998
|
if (members.size === 0)
|
|
@@ -3598,19 +4082,24 @@ function detectDictionaryLike(predicate) {
|
|
|
3598
4082
|
*/
|
|
3599
4083
|
const dictionaryLikeCache = new WeakMap();
|
|
3600
4084
|
function dictionaryLikeMatches(dictionary, pattern, caseInsensitive, escape) {
|
|
4085
|
+
const externalPattern = externalSqlDomainValue(pattern);
|
|
4086
|
+
if (typeof externalPattern !== "string")
|
|
4087
|
+
throw new TypeError("LIKE requires string operands");
|
|
3601
4088
|
let patterns = dictionaryLikeCache.get(dictionary);
|
|
3602
4089
|
if (patterns === undefined) {
|
|
3603
4090
|
patterns = new Map();
|
|
3604
4091
|
dictionaryLikeCache.set(dictionary, patterns);
|
|
3605
4092
|
}
|
|
3606
|
-
const key = `${caseInsensitive ? "i" : "s"}${escape ?? ""} ${
|
|
4093
|
+
const key = `${caseInsensitive ? "i" : "s"}${escape ?? ""} ${externalPattern}`;
|
|
3607
4094
|
let matches = patterns.get(key);
|
|
3608
4095
|
if (matches === undefined) {
|
|
3609
4096
|
matches = new Uint8Array(dictionary.length);
|
|
3610
4097
|
for (let index = 0; index < dictionary.length; index += 1) {
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
4098
|
+
const value = externalSqlDomainValue(dictionary[index] ?? "");
|
|
4099
|
+
matches[index] =
|
|
4100
|
+
typeof value === "string" && likeMatches(externalPattern, value, caseInsensitive, escape)
|
|
4101
|
+
? 1
|
|
4102
|
+
: 0;
|
|
3614
4103
|
}
|
|
3615
4104
|
if (patterns.size >= 32)
|
|
3616
4105
|
patterns.clear();
|
|
@@ -3709,17 +4198,24 @@ function booleanTruth(expression, evaluateValue) {
|
|
|
3709
4198
|
if (operator === "LIKE" ||
|
|
3710
4199
|
operator === "NOT LIKE" ||
|
|
3711
4200
|
operator === "ILIKE" ||
|
|
3712
|
-
operator === "NOT ILIKE"
|
|
3713
|
-
|
|
3714
|
-
|
|
4201
|
+
operator === "NOT ILIKE" ||
|
|
4202
|
+
operator === "SIMILAR TO" ||
|
|
4203
|
+
operator === "NOT SIMILAR TO") {
|
|
4204
|
+
const value = externalSqlDomainValue(evaluateValue(expression.left));
|
|
4205
|
+
const pattern = externalSqlDomainValue(evaluateValue(expression.right));
|
|
3715
4206
|
if (value === null || value === undefined || pattern === null || pattern === undefined) {
|
|
3716
4207
|
return null;
|
|
3717
4208
|
}
|
|
3718
4209
|
if (typeof value !== "string" || typeof pattern !== "string") {
|
|
3719
4210
|
throw new TypeError("LIKE requires string operands");
|
|
3720
4211
|
}
|
|
3721
|
-
const
|
|
3722
|
-
|
|
4212
|
+
const similar = operator === "SIMILAR TO" || operator === "NOT SIMILAR TO";
|
|
4213
|
+
const matched = similar
|
|
4214
|
+
? compileSimilarPattern(pattern, expression.escape ?? "\\").test(value)
|
|
4215
|
+
: likeMatches(pattern, value, operator === "ILIKE" || operator === "NOT ILIKE", expression.escape);
|
|
4216
|
+
return operator === "LIKE" || operator === "ILIKE" || operator === "SIMILAR TO"
|
|
4217
|
+
? matched
|
|
4218
|
+
: !matched;
|
|
3723
4219
|
}
|
|
3724
4220
|
if (operator === "IS DISTINCT FROM" || operator === "IS NOT DISTINCT FROM") {
|
|
3725
4221
|
const distinct = distinctFromComparison(evaluateValue(expression.left), evaluateValue(expression.right));
|
|
@@ -3732,9 +4228,9 @@ function booleanTruth(expression, evaluateValue) {
|
|
|
3732
4228
|
const a = comparable(left);
|
|
3733
4229
|
const b = comparable(right);
|
|
3734
4230
|
if (operator === "=")
|
|
3735
|
-
return a ===
|
|
4231
|
+
return compareValues(a, b) === 0;
|
|
3736
4232
|
if (operator === "!=" || operator === "<>")
|
|
3737
|
-
return a !==
|
|
4233
|
+
return compareValues(a, b) !== 0;
|
|
3738
4234
|
const comparison = compareValues(a, b);
|
|
3739
4235
|
if (operator === ">")
|
|
3740
4236
|
return comparison > 0;
|
|
@@ -3933,8 +4429,11 @@ function binaryValue(operator, left, right) {
|
|
|
3933
4429
|
if (typeof left !== "string" || typeof right !== "string") {
|
|
3934
4430
|
throw new TypeError("|| requires string operands");
|
|
3935
4431
|
}
|
|
3936
|
-
return left + right;
|
|
4432
|
+
return protectedSqlTextValue(String(externalSqlDomainValue(left)) + String(externalSqlDomainValue(right)));
|
|
3937
4433
|
}
|
|
4434
|
+
const exact = exactNumericBinary(operator, left, right);
|
|
4435
|
+
if (exact !== undefined)
|
|
4436
|
+
return exact;
|
|
3938
4437
|
const a = numeric(left);
|
|
3939
4438
|
const b = numeric(right);
|
|
3940
4439
|
if (operator === "+")
|
|
@@ -3963,7 +4462,11 @@ function comparisonValue(operator, leftValue, rightValue) {
|
|
|
3963
4462
|
if (operator === "LIKE" ||
|
|
3964
4463
|
operator === "NOT LIKE" ||
|
|
3965
4464
|
operator === "ILIKE" ||
|
|
3966
|
-
operator === "NOT ILIKE"
|
|
4465
|
+
operator === "NOT ILIKE" ||
|
|
4466
|
+
operator === "SIMILAR TO" ||
|
|
4467
|
+
operator === "NOT SIMILAR TO") {
|
|
4468
|
+
leftValue = externalSqlDomainValue(leftValue);
|
|
4469
|
+
rightValue = externalSqlDomainValue(rightValue);
|
|
3967
4470
|
if (leftValue === null ||
|
|
3968
4471
|
leftValue === undefined ||
|
|
3969
4472
|
rightValue === null ||
|
|
@@ -3973,8 +4476,13 @@ function comparisonValue(operator, leftValue, rightValue) {
|
|
|
3973
4476
|
if (typeof leftValue !== "string" || typeof rightValue !== "string") {
|
|
3974
4477
|
throw new TypeError("LIKE requires string operands");
|
|
3975
4478
|
}
|
|
3976
|
-
const
|
|
3977
|
-
|
|
4479
|
+
const similar = operator === "SIMILAR TO" || operator === "NOT SIMILAR TO";
|
|
4480
|
+
const matched = similar
|
|
4481
|
+
? compileSimilarPattern(rightValue).test(leftValue)
|
|
4482
|
+
: likeMatches(rightValue, leftValue, operator === "ILIKE" || operator === "NOT ILIKE");
|
|
4483
|
+
return operator === "LIKE" || operator === "ILIKE" || operator === "SIMILAR TO"
|
|
4484
|
+
? matched
|
|
4485
|
+
: !matched;
|
|
3978
4486
|
}
|
|
3979
4487
|
if (leftValue === null ||
|
|
3980
4488
|
leftValue === undefined ||
|
|
@@ -3985,9 +4493,9 @@ function comparisonValue(operator, leftValue, rightValue) {
|
|
|
3985
4493
|
const left = comparable(leftValue);
|
|
3986
4494
|
const right = comparable(rightValue);
|
|
3987
4495
|
if (operator === "=")
|
|
3988
|
-
return left ===
|
|
4496
|
+
return compareValues(left, right) === 0;
|
|
3989
4497
|
if (operator === "!=" || operator === "<>")
|
|
3990
|
-
return left !==
|
|
4498
|
+
return compareValues(left, right) !== 0;
|
|
3991
4499
|
const comparison = compareValues(left, right);
|
|
3992
4500
|
if (operator === ">")
|
|
3993
4501
|
return comparison > 0;
|
|
@@ -3998,7 +4506,7 @@ function comparisonValue(operator, leftValue, rightValue) {
|
|
|
3998
4506
|
return comparison <= 0;
|
|
3999
4507
|
}
|
|
4000
4508
|
function comparable(value) {
|
|
4001
|
-
return value instanceof Date ? value
|
|
4509
|
+
return value instanceof Date ? dateMilliseconds(value) : value;
|
|
4002
4510
|
}
|
|
4003
4511
|
function groupKey(value) {
|
|
4004
4512
|
const comparableValue = comparable(value);
|
|
@@ -4039,8 +4547,17 @@ function stableSortRows(rows, orderBy) {
|
|
|
4039
4547
|
}
|
|
4040
4548
|
}
|
|
4041
4549
|
function compareValues(left, right) {
|
|
4042
|
-
const
|
|
4043
|
-
|
|
4550
|
+
const collated = collatedDomainCompare(left, right);
|
|
4551
|
+
if (collated !== undefined)
|
|
4552
|
+
return collated;
|
|
4553
|
+
const enumOrder = enumDomainCompare(left, right);
|
|
4554
|
+
if (enumOrder !== undefined)
|
|
4555
|
+
return enumOrder;
|
|
4556
|
+
const exact = exactNumericCompare(left, right);
|
|
4557
|
+
if (exact !== undefined)
|
|
4558
|
+
return exact;
|
|
4559
|
+
const a = left instanceof Date ? dateMilliseconds(left) : left;
|
|
4560
|
+
const b = right instanceof Date ? dateMilliseconds(right) : right;
|
|
4044
4561
|
if (a === b)
|
|
4045
4562
|
return 0;
|
|
4046
4563
|
if (a === null || a === undefined)
|
|
@@ -4120,4 +4637,3 @@ function safeMemoryProduct(left, right, label) {
|
|
|
4120
4637
|
}
|
|
4121
4638
|
return product;
|
|
4122
4639
|
}
|
|
4123
|
-
//# sourceMappingURL=vector.js.map
|