@minnowdb/core 0.4.1 → 0.6.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 +5 -5
- package/dist/engine/cancellation.d.ts +2 -0
- package/dist/engine/cancellation.js +4 -0
- package/dist/engine/catalog.d.ts +5 -1
- package/dist/engine/catalog.js +5 -1
- package/dist/engine/client.d.ts +34 -6
- package/dist/engine/client.js +87 -19
- package/dist/engine/database.d.ts +43 -20
- package/dist/engine/database.js +823 -164
- package/dist/engine/defaults.js +11 -0
- package/dist/engine/errors.d.ts +19 -0
- package/dist/engine/errors.js +31 -0
- package/dist/engine/fts.d.ts +2 -15
- package/dist/engine/live.d.ts +1 -7
- package/dist/engine/live.js +12 -13
- package/dist/engine/optimizer.js +546 -39
- package/dist/engine/query-cache.js +1 -0
- package/dist/engine/query.d.ts +16 -278
- package/dist/engine/query.js +260 -74
- package/dist/engine/result-wire.d.ts +2 -0
- package/dist/engine/result-wire.js +21 -5
- package/dist/engine/schema-wire.d.ts +14 -1
- package/dist/engine/schema-wire.js +7 -1
- package/dist/engine/schema.d.ts +83 -32
- package/dist/engine/schema.js +180 -14
- package/dist/engine/sql-domains.d.ts +11 -0
- package/dist/engine/sql-domains.js +65 -1
- package/dist/engine/sql-json.js +22 -3
- package/dist/engine/sql-semantics.js +21 -3
- package/dist/engine/vector.d.ts +2 -2
- package/dist/engine/vector.js +328 -79
- package/dist/engine/worker-host.js +119 -44
- package/dist/plan/index.d.ts +5 -4
- package/dist/plan/index.js +3 -3
- package/dist/plan/model.d.ts +218 -0
- package/dist/plan/model.js +1 -0
- package/dist/storage/indexeddb.js +4 -12
- package/dist/storage/toolkit/record-core.js +7 -22
- package/dist/storage/types.d.ts +26 -8
- package/dist/storage/types.js +85 -0
- package/dist/transactions/index.d.ts +5 -3
- package/dist/transactions/index.js +58 -8
- package/dist/worker-protocol/index.d.ts +6 -1
- package/dist/worker-protocol/index.js +5 -2
- package/package.json +1 -1
- package/postgres-feature-profile.json +5 -0
- package/sql-feature-matrix.json +75 -19
package/dist/engine/vector.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { dateMilliseconds } from "../date-value.js";
|
|
2
2
|
import { MAX_TEMP_RUN_BATCH_BYTES, MAX_TEMP_RUN_PAGE_BYTES, MAX_TEMP_RUN_PAGES_PER_BATCH, } from "../storage/types.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { throwIfAborted } from "./cancellation.js";
|
|
4
|
+
import { cachedListMembership, childExpressions, distinctFromComparison, nullOrder, isScalarFunctionName, likeMatches, orderOutputName, parseQuantified, quantifiedComparison, scalarFunctionValue, unknownColumnDomains, } from "./query.js";
|
|
5
|
+
import { jsonConstructor } from "./sql-json.js";
|
|
5
6
|
import { bm25DocumentScore, cachedQueryTerms, FtsStatsAccumulator, fullTermsMask, renderDocumentValue, termFrequencies, termsMask, tokenize, } from "./fts.js";
|
|
6
7
|
import { ByteGroupIndex } from "./group-index.js";
|
|
7
8
|
import { ByteJoinIndex } from "./join-index.js";
|
|
9
|
+
import { UnknownTableError } from "./errors.js";
|
|
8
10
|
import { QueryMemoryBudgetError, QueryMemoryContext, } from "./memory.js";
|
|
9
|
-
import {
|
|
10
|
-
import { exactNumericBinary,
|
|
11
|
+
import { compareSqlValues, compileSimilarPattern, defineSqlResultProperty, } from "./sql-semantics.js";
|
|
12
|
+
import { exactNumericBinary, externalSqlDomainValue, isDateDomainValue, isExactNumeric, preservedJsonDomainValue, protectedSqlTextValue, } from "./sql-domains.js";
|
|
11
13
|
import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
|
|
12
14
|
const DEFAULT_BATCH_ROWS = 2_048;
|
|
13
15
|
/** Above this, locating each IN member separately costs more than scanning between them. */
|
|
@@ -41,11 +43,14 @@ function boundedSpillPageRows(value) {
|
|
|
41
43
|
return Math.min(rows, DEFAULT_BATCH_ROWS);
|
|
42
44
|
}
|
|
43
45
|
/** Writes already-materialized pages through the batch method when the store offers one. */
|
|
44
|
-
async function writeSpillPages(store, pages) {
|
|
46
|
+
async function writeSpillPages(store, pages, signal) {
|
|
47
|
+
throwIfAborted(signal);
|
|
45
48
|
const batched = store.putPages?.bind(store);
|
|
46
49
|
if (batched === undefined) {
|
|
47
50
|
for (const page of pages) {
|
|
51
|
+
throwIfAborted(signal);
|
|
48
52
|
await store.putPage(page.ownerId, page.runId, page.pageIndex, page.bytes);
|
|
53
|
+
throwIfAborted(signal);
|
|
49
54
|
}
|
|
50
55
|
return;
|
|
51
56
|
}
|
|
@@ -59,24 +64,28 @@ async function writeSpillPages(store, pages) {
|
|
|
59
64
|
(batch.length === SPILL_WRITE_BATCH_PAGES ||
|
|
60
65
|
batchBytes + page.bytes.byteLength > MAX_TEMP_RUN_BATCH_BYTES)) {
|
|
61
66
|
await batched(batch);
|
|
67
|
+
throwIfAborted(signal);
|
|
62
68
|
batch = [];
|
|
63
69
|
batchBytes = 0;
|
|
64
70
|
}
|
|
65
71
|
batch.push(page);
|
|
66
72
|
batchBytes += page.bytes.byteLength;
|
|
67
73
|
}
|
|
68
|
-
if (batch.length > 0)
|
|
74
|
+
if (batch.length > 0) {
|
|
69
75
|
await batched(batch);
|
|
76
|
+
throwIfAborted(signal);
|
|
77
|
+
}
|
|
70
78
|
}
|
|
71
|
-
async function writeSpillRowPages(store, ownerId, runId, startPageIndex, columns, rows, pageRows) {
|
|
79
|
+
async function writeSpillRowPages(store, ownerId, runId, startPageIndex, columns, rows, pageRows, signal) {
|
|
72
80
|
let pending = [];
|
|
73
81
|
let pendingBytes = 0;
|
|
74
82
|
let pageCount = 0;
|
|
75
83
|
for (const bytes of encodeSpillRowPages(columns, rows, pageRows)) {
|
|
84
|
+
throwIfAborted(signal);
|
|
76
85
|
if (pending.length > 0 &&
|
|
77
86
|
(pending.length === SPILL_WRITE_BATCH_PAGES ||
|
|
78
87
|
pendingBytes + bytes.byteLength > MAX_TEMP_RUN_BATCH_BYTES)) {
|
|
79
|
-
await writeSpillPages(store, pending);
|
|
88
|
+
await writeSpillPages(store, pending, signal);
|
|
80
89
|
pending = [];
|
|
81
90
|
pendingBytes = 0;
|
|
82
91
|
}
|
|
@@ -84,7 +93,7 @@ async function writeSpillRowPages(store, ownerId, runId, startPageIndex, columns
|
|
|
84
93
|
pendingBytes += bytes.byteLength;
|
|
85
94
|
pageCount += 1;
|
|
86
95
|
}
|
|
87
|
-
await writeSpillPages(store, pending);
|
|
96
|
+
await writeSpillPages(store, pending, signal);
|
|
88
97
|
return pageCount;
|
|
89
98
|
}
|
|
90
99
|
export function createColumnarTable(name, columns, uniqueKey) {
|
|
@@ -156,13 +165,17 @@ export function prepareVectorQuery(plan, inputTables, options = {}) {
|
|
|
156
165
|
async executeAsync(executionOptions = {}) {
|
|
157
166
|
if (closed)
|
|
158
167
|
throw new Error("Prepared vector query is closed");
|
|
168
|
+
throwIfAborted(executionOptions.signal);
|
|
159
169
|
const canSpillSort = bound.orderBy.length > 0 && !bound.grouped && !bound.sourceOrdered;
|
|
160
170
|
// An unordered grouped plan spills too: the empty ordering makes the pairwise merge a
|
|
161
171
|
// stable concatenation, and partition-wise accumulation bounds peak group state.
|
|
162
172
|
const canSpillHash = bound.grouped && bound.groupBy.length > 0;
|
|
163
173
|
if (executionOptions.spillStore === undefined || (!canSpillSort && !canSpillHash)) {
|
|
164
|
-
if (executionOptions.loadScanWindow === undefined)
|
|
165
|
-
|
|
174
|
+
if (executionOptions.loadScanWindow === undefined) {
|
|
175
|
+
const result = this.execute();
|
|
176
|
+
throwIfAborted(executionOptions.signal);
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
166
179
|
const executionMemory = retainedMemory.createChild();
|
|
167
180
|
try {
|
|
168
181
|
return await executeBoundPlanAsync(bound, executionMemory, executionOptions);
|
|
@@ -559,7 +572,7 @@ function bindPlan(plan, tables, memory, ftsStats) {
|
|
|
559
572
|
const sourceTables = sources.map((source) => {
|
|
560
573
|
const table = tables.get(source.table);
|
|
561
574
|
if (table === undefined)
|
|
562
|
-
throw new
|
|
575
|
+
throw new UnknownTableError(source.table);
|
|
563
576
|
return table;
|
|
564
577
|
});
|
|
565
578
|
const sourceAliases = sources.map((source) => source.alias);
|
|
@@ -588,6 +601,9 @@ function bindPlan(plan, tables, memory, ftsStats) {
|
|
|
588
601
|
const dictionaryLike = detectDictionaryLike(bound);
|
|
589
602
|
if (dictionaryLike !== undefined)
|
|
590
603
|
return { ...bound, dictionaryLike };
|
|
604
|
+
const dictionaryNumeric = detectDictionaryNumericComparison(bound);
|
|
605
|
+
if (dictionaryNumeric !== undefined)
|
|
606
|
+
return { ...bound, dictionaryNumeric };
|
|
591
607
|
const primitive = detectPrimitiveComparison(bound);
|
|
592
608
|
if (primitive !== undefined)
|
|
593
609
|
return { ...bound, primitive };
|
|
@@ -598,8 +614,11 @@ function bindPlan(plan, tables, memory, ftsStats) {
|
|
|
598
614
|
const bound = bindPredicate(predicate);
|
|
599
615
|
if (bound.primitive !== undefined || bound.primitiveIn !== undefined)
|
|
600
616
|
return bound;
|
|
601
|
-
if (bound.dictionaryEquality !== undefined ||
|
|
617
|
+
if (bound.dictionaryEquality !== undefined ||
|
|
618
|
+
bound.dictionaryLike !== undefined ||
|
|
619
|
+
bound.dictionaryNumeric !== undefined) {
|
|
602
620
|
return bound;
|
|
621
|
+
}
|
|
603
622
|
const branches = disjunctiveNormalForm(predicate);
|
|
604
623
|
if (branches === undefined)
|
|
605
624
|
return bound;
|
|
@@ -651,10 +670,10 @@ function bindPlan(plan, tables, memory, ftsStats) {
|
|
|
651
670
|
});
|
|
652
671
|
// A wildcard select projects the materialized columns of each source, so ORDER BY resolves
|
|
653
672
|
// against those same names.
|
|
654
|
-
const orderSources = sources.
|
|
655
|
-
|
|
656
|
-
columns: [
|
|
657
|
-
})
|
|
673
|
+
const orderSources = sources.flatMap((source, index) => {
|
|
674
|
+
const columns = [...(sourceTables[index]?.columns.keys() ?? [])].filter((name) => !name.startsWith("\0"));
|
|
675
|
+
return columns.length === 0 ? [] : [{ alias: source.alias, columns }];
|
|
676
|
+
});
|
|
658
677
|
const orderBy = plan.orderBy.map(({ expression, direction, nulls }) => ({
|
|
659
678
|
outputName: orderOutputName(expression, plan.select, orderSources),
|
|
660
679
|
direction,
|
|
@@ -1216,6 +1235,7 @@ function executeBoundPlan(plan, memory) {
|
|
|
1216
1235
|
return finishResult(plan, rows, memory);
|
|
1217
1236
|
}
|
|
1218
1237
|
async function executeBoundPlanAsync(plan, memory, options) {
|
|
1238
|
+
throwIfAborted(options.signal);
|
|
1219
1239
|
const metadataCount = executeMetadataCount(plan, memory);
|
|
1220
1240
|
if (metadataCount !== undefined)
|
|
1221
1241
|
return metadataCount;
|
|
@@ -1223,11 +1243,13 @@ async function executeBoundPlanAsync(plan, memory, options) {
|
|
|
1223
1243
|
const output = new ResultSink(plan, memory, options.loadScanWindow === undefined);
|
|
1224
1244
|
const scanRows = plan.sourceTables[plan.scanSource]?.rowCount ?? 0;
|
|
1225
1245
|
for (let start = 0; start < scanRows;) {
|
|
1246
|
+
throwIfAborted(options.signal);
|
|
1226
1247
|
let length = Math.min(DEFAULT_BATCH_ROWS, scanRows - start);
|
|
1227
1248
|
// The loader answers synchronously when the batch is already resident — the common case,
|
|
1228
1249
|
// every batch but the first per block — so the scan loop only pays await on real slides.
|
|
1229
1250
|
const loaded = options.loadScanWindow?.(start, length);
|
|
1230
1251
|
const residentEnd = typeof loaded === "number" || loaded === undefined ? loaded : await loaded;
|
|
1252
|
+
throwIfAborted(options.signal);
|
|
1231
1253
|
if (typeof residentEnd === "number" && residentEnd > start) {
|
|
1232
1254
|
length = Math.min(length, residentEnd - start);
|
|
1233
1255
|
}
|
|
@@ -1249,6 +1271,7 @@ async function executeBoundPlanAsync(plan, memory, options) {
|
|
|
1249
1271
|
const ranges = narrowed.ranges ?? [{ begin: narrowed.begin, end: narrowed.end }];
|
|
1250
1272
|
for (const range of ranges) {
|
|
1251
1273
|
for (let row = range.begin; row < range.end; row += DEFAULT_BATCH_ROWS) {
|
|
1274
|
+
throwIfAborted(options.signal);
|
|
1252
1275
|
const rows = Math.min(DEFAULT_BATCH_ROWS, range.end - row);
|
|
1253
1276
|
if (runScanBatch(plan, row, rows, groups, output, memory)) {
|
|
1254
1277
|
stopped = true;
|
|
@@ -1262,6 +1285,7 @@ async function executeBoundPlanAsync(plan, memory, options) {
|
|
|
1262
1285
|
break;
|
|
1263
1286
|
start = windowEnd;
|
|
1264
1287
|
}
|
|
1288
|
+
throwIfAborted(options.signal);
|
|
1265
1289
|
const rows = plan.grouped ? finishGroups(plan, groups.values(), memory) : output.finish();
|
|
1266
1290
|
return finishResult(plan, rows, memory);
|
|
1267
1291
|
}
|
|
@@ -1274,24 +1298,44 @@ async function executeBoundPlanBatches(plan, memory, options, consume) {
|
|
|
1274
1298
|
const columns = plan.wildcard ? wildcardColumnNames(plan) : plan.select.map((item) => item.alias);
|
|
1275
1299
|
const { limit, offset, ...unbounded } = plan;
|
|
1276
1300
|
const scanPlan = unbounded;
|
|
1301
|
+
const consumeFirstColumn = options.consumeFirstColumn;
|
|
1302
|
+
const firstExpression = consumeFirstColumn === undefined ? undefined : scanPlan.select[0]?.expression;
|
|
1277
1303
|
let skipped = 0;
|
|
1278
1304
|
let emitted = 0;
|
|
1279
1305
|
const scanRows = scanPlan.sourceTables[scanPlan.scanSource]?.rowCount ?? 0;
|
|
1280
1306
|
const step = Math.min(DEFAULT_BATCH_ROWS, options.batchRows);
|
|
1281
1307
|
for (let start = 0; start < scanRows && (limit === undefined || emitted < limit);) {
|
|
1282
|
-
options.signal
|
|
1308
|
+
throwIfAborted(options.signal);
|
|
1283
1309
|
let length = Math.min(step, scanRows - start);
|
|
1284
1310
|
const loaded = options.loadScanWindow?.(start, length);
|
|
1285
1311
|
const residentEnd = typeof loaded === "number" || loaded === undefined ? loaded : await loaded;
|
|
1286
|
-
options.signal
|
|
1312
|
+
throwIfAborted(options.signal);
|
|
1287
1313
|
if (typeof residentEnd === "number" && residentEnd > start) {
|
|
1288
1314
|
length = Math.min(length, residentEnd - start);
|
|
1289
1315
|
}
|
|
1290
1316
|
const pageMemory = memory.createChild();
|
|
1291
1317
|
try {
|
|
1292
1318
|
const groups = new GroupAccumulator(scanPlan, pageMemory);
|
|
1293
|
-
const
|
|
1319
|
+
const values = [];
|
|
1320
|
+
const output = firstExpression === undefined
|
|
1321
|
+
? new ResultSink(scanPlan, pageMemory, options.loadScanWindow === undefined)
|
|
1322
|
+
: {
|
|
1323
|
+
get size() {
|
|
1324
|
+
return values.length;
|
|
1325
|
+
},
|
|
1326
|
+
tryAddBatch: () => false,
|
|
1327
|
+
add: (batch, row) => {
|
|
1328
|
+
values.push(asQueryValue(evaluateBatchExpression(scanPlan, firstExpression, batch, row)));
|
|
1329
|
+
},
|
|
1330
|
+
finish: () => [],
|
|
1331
|
+
};
|
|
1294
1332
|
runScanBatch(scanPlan, start, length, groups, output, pageMemory);
|
|
1333
|
+
if (firstExpression !== undefined && consumeFirstColumn !== undefined) {
|
|
1334
|
+
if (values.length > 0)
|
|
1335
|
+
await consumeFirstColumn(values);
|
|
1336
|
+
start += length;
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1295
1339
|
let rows = output.finish();
|
|
1296
1340
|
const remainingOffset = Math.max(0, (offset ?? 0) - skipped);
|
|
1297
1341
|
if (remainingOffset > 0) {
|
|
@@ -1304,7 +1348,11 @@ async function executeBoundPlanBatches(plan, memory, options, consume) {
|
|
|
1304
1348
|
}
|
|
1305
1349
|
if (rows.length > 0) {
|
|
1306
1350
|
emitted += rows.length;
|
|
1307
|
-
await consume({
|
|
1351
|
+
await consume({
|
|
1352
|
+
columns: [...columns],
|
|
1353
|
+
columnDomains: unknownColumnDomains(columns),
|
|
1354
|
+
rows,
|
|
1355
|
+
});
|
|
1308
1356
|
}
|
|
1309
1357
|
}
|
|
1310
1358
|
finally {
|
|
@@ -1318,6 +1366,7 @@ function createSpillOwnerId() {
|
|
|
1318
1366
|
return `query-${globalThis.crypto.randomUUID()}`;
|
|
1319
1367
|
}
|
|
1320
1368
|
async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
1369
|
+
throwIfAborted(options.signal);
|
|
1321
1370
|
const store = required(options.spillStore, "Query spill store is missing");
|
|
1322
1371
|
const pageRows = boundedSpillPageRows(options.spillPageRows);
|
|
1323
1372
|
const columns = plan.wildcard ? wildcardColumnNames(plan) : plan.select.map((item) => item.alias);
|
|
@@ -1328,9 +1377,11 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
|
1328
1377
|
const scanRows = plan.sourceTables[plan.scanSource]?.rowCount ?? 0;
|
|
1329
1378
|
const scanBatchRows = Math.min(DEFAULT_BATCH_ROWS, pageRows);
|
|
1330
1379
|
for (let start = 0; start < scanRows;) {
|
|
1380
|
+
throwIfAborted(options.signal);
|
|
1331
1381
|
let length = Math.min(scanBatchRows, scanRows - start);
|
|
1332
1382
|
const loadedSort = options.loadScanWindow?.(start, length);
|
|
1333
1383
|
const residentEnd = typeof loadedSort === "number" || loadedSort === undefined ? loadedSort : await loadedSort;
|
|
1384
|
+
throwIfAborted(options.signal);
|
|
1334
1385
|
if (typeof residentEnd === "number" && residentEnd > start) {
|
|
1335
1386
|
length = Math.min(length, residentEnd - start);
|
|
1336
1387
|
}
|
|
@@ -1346,6 +1397,7 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
|
1346
1397
|
for (let index = 0; index < length; index += 1)
|
|
1347
1398
|
scan[index] = start + index;
|
|
1348
1399
|
await spillJoinedBatches(plan, { length, rowsBySource: sourceRows, memory: batchMemory }, 0, memory, async (batch) => {
|
|
1400
|
+
throwIfAborted(options.signal);
|
|
1349
1401
|
const outputMemory = memory.createChild();
|
|
1350
1402
|
try {
|
|
1351
1403
|
const rows = [];
|
|
@@ -1360,13 +1412,13 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
|
1360
1412
|
ordering.release();
|
|
1361
1413
|
}
|
|
1362
1414
|
const runId = `run-${String(runSequence++)}`;
|
|
1363
|
-
const pageCount = await writeSpillRowPages(store, ownerId, runId, 0, columns, rows, pageRows);
|
|
1415
|
+
const pageCount = await writeSpillRowPages(store, ownerId, runId, 0, columns, rows, pageRows, options.signal);
|
|
1364
1416
|
runs.push({ id: runId, pageCount });
|
|
1365
1417
|
}
|
|
1366
1418
|
finally {
|
|
1367
1419
|
outputMemory.close();
|
|
1368
1420
|
}
|
|
1369
|
-
});
|
|
1421
|
+
}, options.signal);
|
|
1370
1422
|
}
|
|
1371
1423
|
finally {
|
|
1372
1424
|
batchMemory.close();
|
|
@@ -1374,11 +1426,13 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
|
1374
1426
|
start += length;
|
|
1375
1427
|
}
|
|
1376
1428
|
if (runs.length === 0)
|
|
1377
|
-
return { columns, rows: [] };
|
|
1429
|
+
return { columns, columnDomains: unknownColumnDomains(columns), rows: [] };
|
|
1378
1430
|
let active = runs;
|
|
1379
1431
|
while (active.length > 1) {
|
|
1432
|
+
throwIfAborted(options.signal);
|
|
1380
1433
|
const merged = [];
|
|
1381
1434
|
for (let index = 0; index < active.length; index += 2) {
|
|
1435
|
+
throwIfAborted(options.signal);
|
|
1382
1436
|
const left = required(active[index], "Left spill run is missing");
|
|
1383
1437
|
const right = active[index + 1];
|
|
1384
1438
|
if (right === undefined) {
|
|
@@ -1386,9 +1440,10 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
|
1386
1440
|
continue;
|
|
1387
1441
|
}
|
|
1388
1442
|
const outputId = `merge-${String(runSequence++)}`;
|
|
1389
|
-
merged.push(await mergeSpillRuns(store, ownerId, left, right, outputId, columns, plan.orderBy, pageRows, memory));
|
|
1443
|
+
merged.push(await mergeSpillRuns(store, ownerId, left, right, outputId, columns, plan.orderBy, pageRows, memory, options.signal));
|
|
1390
1444
|
await store.removeRun(ownerId, left.id);
|
|
1391
1445
|
await store.removeRun(ownerId, right.id);
|
|
1446
|
+
throwIfAborted(options.signal);
|
|
1392
1447
|
}
|
|
1393
1448
|
active = merged;
|
|
1394
1449
|
}
|
|
@@ -1397,7 +1452,9 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
|
1397
1452
|
const offset = plan.offset ?? 0;
|
|
1398
1453
|
const limit = plan.limit === undefined ? Number.MAX_SAFE_INTEGER : plan.limit + offset;
|
|
1399
1454
|
for (let pageIndex = 0; pageIndex < finalRun.pageCount && rows.length < limit; pageIndex += 1) {
|
|
1455
|
+
throwIfAborted(options.signal);
|
|
1400
1456
|
const bytes = await store.getPage(ownerId, finalRun.id, pageIndex);
|
|
1457
|
+
throwIfAborted(options.signal);
|
|
1401
1458
|
if (bytes === undefined)
|
|
1402
1459
|
throw new Error("Query spill page is missing");
|
|
1403
1460
|
for (const row of decodeSpillRows(columns, bytes)) {
|
|
@@ -1408,13 +1465,14 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
|
1408
1465
|
}
|
|
1409
1466
|
if (offset > 0)
|
|
1410
1467
|
rows.splice(0, Math.min(offset, rows.length));
|
|
1411
|
-
return { columns, rows };
|
|
1468
|
+
return { columns, columnDomains: unknownColumnDomains(columns), rows };
|
|
1412
1469
|
}
|
|
1413
1470
|
finally {
|
|
1414
1471
|
await store.removeOwner(ownerId);
|
|
1415
1472
|
}
|
|
1416
1473
|
}
|
|
1417
1474
|
async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
1475
|
+
throwIfAborted(options.signal);
|
|
1418
1476
|
const store = required(options.spillStore, "Query spill store is missing");
|
|
1419
1477
|
const pageRows = boundedSpillPageRows(options.spillPageRows);
|
|
1420
1478
|
const partitionCount = 64;
|
|
@@ -1431,9 +1489,11 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1431
1489
|
// page size while staying coarse enough to amortize partition-page write transactions.
|
|
1432
1490
|
const scanChunkRows = Math.min(DEFAULT_BATCH_ROWS, HASH_SPILL_SCAN_CHUNK_ROWS);
|
|
1433
1491
|
for (let start = 0; start < scanRows;) {
|
|
1492
|
+
throwIfAborted(options.signal);
|
|
1434
1493
|
let length = Math.min(scanChunkRows, scanRows - start);
|
|
1435
1494
|
const loadedHash = options.loadScanWindow?.(start, length);
|
|
1436
1495
|
const residentEnd = typeof loadedHash === "number" || loadedHash === undefined ? loadedHash : await loadedHash;
|
|
1496
|
+
throwIfAborted(options.signal);
|
|
1437
1497
|
if (typeof residentEnd === "number" && residentEnd > start) {
|
|
1438
1498
|
length = Math.min(length, residentEnd - start);
|
|
1439
1499
|
}
|
|
@@ -1453,6 +1513,7 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1453
1513
|
// Each surviving row spills its evaluated group keys and aggregate arguments, so the
|
|
1454
1514
|
// partition phase never re-reads source vectors and the scan source may be windowed.
|
|
1455
1515
|
async (batch) => {
|
|
1516
|
+
throwIfAborted(options.signal);
|
|
1456
1517
|
for (let row = 0; row < batch.length; row += 1) {
|
|
1457
1518
|
if (!plan.predicates.every((predicate) => evaluateBatchPredicate(plan, predicate, batch, row))) {
|
|
1458
1519
|
continue;
|
|
@@ -1486,7 +1547,7 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1486
1547
|
rows.push(spillRow);
|
|
1487
1548
|
partitionBuffers.set(partition, rows);
|
|
1488
1549
|
}
|
|
1489
|
-
});
|
|
1550
|
+
}, options.signal);
|
|
1490
1551
|
const flush = [];
|
|
1491
1552
|
let flushBytes = 0;
|
|
1492
1553
|
for (const [partition, rows] of partitionBuffers) {
|
|
@@ -1494,7 +1555,7 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1494
1555
|
if (flush.length > 0 &&
|
|
1495
1556
|
(flush.length === SPILL_WRITE_BATCH_PAGES ||
|
|
1496
1557
|
flushBytes + bytes.byteLength > MAX_TEMP_RUN_BATCH_BYTES)) {
|
|
1497
|
-
await writeSpillPages(store, flush);
|
|
1558
|
+
await writeSpillPages(store, flush, options.signal);
|
|
1498
1559
|
flush.length = 0;
|
|
1499
1560
|
flushBytes = 0;
|
|
1500
1561
|
}
|
|
@@ -1509,7 +1570,7 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1509
1570
|
partitionPages[partition] = pageIndex + 1;
|
|
1510
1571
|
}
|
|
1511
1572
|
}
|
|
1512
|
-
await writeSpillPages(store, flush);
|
|
1573
|
+
await writeSpillPages(store, flush, options.signal);
|
|
1513
1574
|
}
|
|
1514
1575
|
finally {
|
|
1515
1576
|
batchMemory.close();
|
|
@@ -1518,6 +1579,7 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1518
1579
|
}
|
|
1519
1580
|
const runs = [];
|
|
1520
1581
|
for (let partition = 0; partition < partitionCount; partition += 1) {
|
|
1582
|
+
throwIfAborted(options.signal);
|
|
1521
1583
|
const sourcePageCount = partitionPages[partition] ?? 0;
|
|
1522
1584
|
if (sourcePageCount === 0)
|
|
1523
1585
|
continue;
|
|
@@ -1525,7 +1587,9 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1525
1587
|
try {
|
|
1526
1588
|
const groups = new ByteGroupIndex(partitionMemory);
|
|
1527
1589
|
for (let pageIndex = 0; pageIndex < sourcePageCount; pageIndex += 1) {
|
|
1590
|
+
throwIfAborted(options.signal);
|
|
1528
1591
|
const bytes = await store.getPage(ownerId, `partition-${String(partition)}`, pageIndex);
|
|
1592
|
+
throwIfAborted(options.signal);
|
|
1529
1593
|
if (bytes === undefined)
|
|
1530
1594
|
throw new Error("Query hash spill page is missing");
|
|
1531
1595
|
const pageMemory = partitionMemory.createChild();
|
|
@@ -1554,7 +1618,7 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1554
1618
|
}
|
|
1555
1619
|
}
|
|
1556
1620
|
const runId = `group-${String(runSequence++)}`;
|
|
1557
|
-
const pageCount = await writeSpillRowPages(store, ownerId, runId, 0, columns, rows, pageRows);
|
|
1621
|
+
const pageCount = await writeSpillRowPages(store, ownerId, runId, 0, columns, rows, pageRows, options.signal);
|
|
1558
1622
|
runs.push({ id: runId, pageCount });
|
|
1559
1623
|
}
|
|
1560
1624
|
finally {
|
|
@@ -1563,10 +1627,10 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1563
1627
|
}
|
|
1564
1628
|
}
|
|
1565
1629
|
if (runs.length === 0)
|
|
1566
|
-
return { columns, rows: [] };
|
|
1567
|
-
const finalRun = await mergeAllSpillRuns(store, ownerId, runs, columns, plan.orderBy, pageRows, () => `merge-${String(runSequence++)}`, memory);
|
|
1630
|
+
return { columns, columnDomains: unknownColumnDomains(columns), rows: [] };
|
|
1631
|
+
const finalRun = await mergeAllSpillRuns(store, ownerId, runs, columns, plan.orderBy, pageRows, () => `merge-${String(runSequence++)}`, memory, options.signal);
|
|
1568
1632
|
const spillOffset = plan.offset ?? 0;
|
|
1569
|
-
const result = await readFinalSpillRun(store, ownerId, finalRun, columns, plan.limit === undefined ? undefined : plan.limit + spillOffset);
|
|
1633
|
+
const result = await readFinalSpillRun(store, ownerId, finalRun, columns, plan.limit === undefined ? undefined : plan.limit + spillOffset, options.signal);
|
|
1570
1634
|
if (spillOffset > 0)
|
|
1571
1635
|
result.rows.splice(0, Math.min(spillOffset, result.rows.length));
|
|
1572
1636
|
return result;
|
|
@@ -1575,11 +1639,13 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1575
1639
|
await store.removeOwner(ownerId);
|
|
1576
1640
|
}
|
|
1577
1641
|
}
|
|
1578
|
-
async function mergeAllSpillRuns(store, ownerId, runs, columns, orderBy, pageRows, nextRunId, memory) {
|
|
1642
|
+
async function mergeAllSpillRuns(store, ownerId, runs, columns, orderBy, pageRows, nextRunId, memory, signal) {
|
|
1579
1643
|
let active = [...runs];
|
|
1580
1644
|
while (active.length > 1) {
|
|
1645
|
+
throwIfAborted(signal);
|
|
1581
1646
|
const merged = [];
|
|
1582
1647
|
for (let index = 0; index < active.length; index += 2) {
|
|
1648
|
+
throwIfAborted(signal);
|
|
1583
1649
|
const left = required(active[index], "Left spill run is missing");
|
|
1584
1650
|
const right = active[index + 1];
|
|
1585
1651
|
if (right === undefined) {
|
|
@@ -1587,19 +1653,22 @@ async function mergeAllSpillRuns(store, ownerId, runs, columns, orderBy, pageRow
|
|
|
1587
1653
|
continue;
|
|
1588
1654
|
}
|
|
1589
1655
|
const outputId = nextRunId();
|
|
1590
|
-
merged.push(await mergeSpillRuns(store, ownerId, left, right, outputId, columns, orderBy, pageRows, memory));
|
|
1656
|
+
merged.push(await mergeSpillRuns(store, ownerId, left, right, outputId, columns, orderBy, pageRows, memory, signal));
|
|
1591
1657
|
await store.removeRun(ownerId, left.id);
|
|
1592
1658
|
await store.removeRun(ownerId, right.id);
|
|
1659
|
+
throwIfAborted(signal);
|
|
1593
1660
|
}
|
|
1594
1661
|
active = merged;
|
|
1595
1662
|
}
|
|
1596
1663
|
return required(active[0], "Final spill run is missing");
|
|
1597
1664
|
}
|
|
1598
|
-
async function readFinalSpillRun(store, ownerId, run, columns, requestedLimit) {
|
|
1665
|
+
async function readFinalSpillRun(store, ownerId, run, columns, requestedLimit, signal) {
|
|
1599
1666
|
const rows = [];
|
|
1600
1667
|
const limit = requestedLimit ?? Number.MAX_SAFE_INTEGER;
|
|
1601
1668
|
for (let pageIndex = 0; pageIndex < run.pageCount && rows.length < limit; pageIndex += 1) {
|
|
1669
|
+
throwIfAborted(signal);
|
|
1602
1670
|
const bytes = await store.getPage(ownerId, run.id, pageIndex);
|
|
1671
|
+
throwIfAborted(signal);
|
|
1603
1672
|
if (bytes === undefined) {
|
|
1604
1673
|
throw new Error(`Query spill page is missing: ${run.id}/${String(pageIndex)}`);
|
|
1605
1674
|
}
|
|
@@ -1609,7 +1678,7 @@ async function readFinalSpillRun(store, ownerId, run, columns, requestedLimit) {
|
|
|
1609
1678
|
rows.push(row);
|
|
1610
1679
|
}
|
|
1611
1680
|
}
|
|
1612
|
-
return { columns: [...columns], rows };
|
|
1681
|
+
return { columns: [...columns], columnDomains: unknownColumnDomains(columns), rows };
|
|
1613
1682
|
}
|
|
1614
1683
|
const hashScratch = new DataView(new ArrayBuffer(8));
|
|
1615
1684
|
/**
|
|
@@ -1646,15 +1715,17 @@ function hashQueryValues(values) {
|
|
|
1646
1715
|
}
|
|
1647
1716
|
return hash;
|
|
1648
1717
|
}
|
|
1649
|
-
async function spillJoinedBatches(plan, batch, joinIndex, memory, consume) {
|
|
1718
|
+
async function spillJoinedBatches(plan, batch, joinIndex, memory, consume, signal) {
|
|
1719
|
+
throwIfAborted(signal);
|
|
1650
1720
|
const join = plan.joins[joinIndex];
|
|
1651
1721
|
if (join === undefined) {
|
|
1652
1722
|
await consume(batch);
|
|
1653
1723
|
return;
|
|
1654
1724
|
}
|
|
1655
1725
|
for (const joined of joinBatches(plan, batch, join, memory)) {
|
|
1726
|
+
throwIfAborted(signal);
|
|
1656
1727
|
try {
|
|
1657
|
-
await spillJoinedBatches(plan, joined, joinIndex + 1, memory, consume);
|
|
1728
|
+
await spillJoinedBatches(plan, joined, joinIndex + 1, memory, consume, signal);
|
|
1658
1729
|
}
|
|
1659
1730
|
finally {
|
|
1660
1731
|
joined.memory?.close();
|
|
@@ -1681,25 +1752,27 @@ function passesPredicates(plan, batch, row) {
|
|
|
1681
1752
|
}
|
|
1682
1753
|
return true;
|
|
1683
1754
|
}
|
|
1684
|
-
async function mergeSpillRuns(store, ownerId, left, right, outputId, columns, orderBy, pageRows, memory) {
|
|
1755
|
+
async function mergeSpillRuns(store, ownerId, left, right, outputId, columns, orderBy, pageRows, memory, signal) {
|
|
1685
1756
|
const mergeMemory = memory.createChild();
|
|
1686
|
-
const leftReader = createSpillRunReader(store, ownerId, left, columns, mergeMemory);
|
|
1687
|
-
const rightReader = createSpillRunReader(store, ownerId, right, columns, mergeMemory);
|
|
1757
|
+
const leftReader = createSpillRunReader(store, ownerId, left, columns, mergeMemory, signal);
|
|
1758
|
+
const rightReader = createSpillRunReader(store, ownerId, right, columns, mergeMemory, signal);
|
|
1688
1759
|
let outputPage = [];
|
|
1689
1760
|
let outputMemory = mergeMemory.createChild();
|
|
1690
1761
|
let pageIndex = 0;
|
|
1691
1762
|
const flush = async () => {
|
|
1692
1763
|
if (outputPage.length === 0)
|
|
1693
1764
|
return;
|
|
1694
|
-
pageIndex += await writeSpillRowPages(store, ownerId, outputId, pageIndex, columns, outputPage, pageRows);
|
|
1765
|
+
pageIndex += await writeSpillRowPages(store, ownerId, outputId, pageIndex, columns, outputPage, pageRows, signal);
|
|
1695
1766
|
outputPage = [];
|
|
1696
1767
|
outputMemory.close();
|
|
1697
1768
|
outputMemory = mergeMemory.createChild();
|
|
1698
1769
|
};
|
|
1699
1770
|
try {
|
|
1771
|
+
throwIfAborted(signal);
|
|
1700
1772
|
let leftRow = await leftReader.next();
|
|
1701
1773
|
let rightRow = await rightReader.next();
|
|
1702
1774
|
while (leftRow !== undefined || rightRow !== undefined) {
|
|
1775
|
+
throwIfAborted(signal);
|
|
1703
1776
|
if (rightRow === undefined ||
|
|
1704
1777
|
(leftRow !== undefined && compareOrderedRows(leftRow, rightRow, orderBy) <= 0)) {
|
|
1705
1778
|
const row = required(leftRow, "Left spill row is missing");
|
|
@@ -1725,17 +1798,19 @@ async function mergeSpillRuns(store, ownerId, left, right, outputId, columns, or
|
|
|
1725
1798
|
mergeMemory.close();
|
|
1726
1799
|
}
|
|
1727
1800
|
}
|
|
1728
|
-
function createSpillRunReader(store, ownerId, run, columns, memory) {
|
|
1801
|
+
function createSpillRunReader(store, ownerId, run, columns, memory, signal) {
|
|
1729
1802
|
let pageIndex = 0;
|
|
1730
1803
|
let rows = [];
|
|
1731
1804
|
let rowIndex = 0;
|
|
1732
1805
|
let pageReservation;
|
|
1733
1806
|
return {
|
|
1734
1807
|
async next() {
|
|
1808
|
+
throwIfAborted(signal);
|
|
1735
1809
|
while (rowIndex >= rows.length) {
|
|
1736
1810
|
if (pageIndex >= run.pageCount)
|
|
1737
1811
|
return undefined;
|
|
1738
1812
|
const bytes = await store.getPage(ownerId, run.id, pageIndex);
|
|
1813
|
+
throwIfAborted(signal);
|
|
1739
1814
|
if (bytes === undefined)
|
|
1740
1815
|
throw new Error("Query spill page is missing");
|
|
1741
1816
|
pageReservation?.release();
|
|
@@ -1909,7 +1984,7 @@ function finishResult(plan, inputRows, memory) {
|
|
|
1909
1984
|
rows.length = Math.min(plan.limit, rows.length);
|
|
1910
1985
|
}
|
|
1911
1986
|
const columns = plan.wildcard ? wildcardColumnNames(plan) : plan.select.map((item) => item.alias);
|
|
1912
|
-
return { columns, rows };
|
|
1987
|
+
return { columns, columnDomains: unknownColumnDomains(columns), rows };
|
|
1913
1988
|
}
|
|
1914
1989
|
/** Source indexes a bound expression reads, for pre-join predicate placement. */
|
|
1915
1990
|
function prefilterSources(expression, into) {
|
|
@@ -2264,6 +2339,43 @@ function filterDictionaryLike(fast, batch, selection, survivors) {
|
|
|
2264
2339
|
}
|
|
2265
2340
|
return kept;
|
|
2266
2341
|
}
|
|
2342
|
+
/** Compacts the selection using a match table computed once per exact-NUMERIC dictionary. */
|
|
2343
|
+
function filterDictionaryNumeric(fast, batch, selection, survivors) {
|
|
2344
|
+
const vector = fast.expression.vector;
|
|
2345
|
+
if (fast.cache.dictionary !== vector.dictionary) {
|
|
2346
|
+
const matches = dictionaryNumericMatches(fast, vector.dictionary);
|
|
2347
|
+
if (matches === undefined)
|
|
2348
|
+
return undefined;
|
|
2349
|
+
fast.cache.dictionary = vector.dictionary;
|
|
2350
|
+
fast.cache.matches = matches;
|
|
2351
|
+
}
|
|
2352
|
+
const matches = fast.cache.matches;
|
|
2353
|
+
const rows = batch.rowsBySource[fast.expression.source];
|
|
2354
|
+
const codes = vector.codes;
|
|
2355
|
+
const validity = vector.validity;
|
|
2356
|
+
const windowStart = vector.window?.start ?? 0;
|
|
2357
|
+
const slots = codes.length;
|
|
2358
|
+
const vectorLength = vector.length;
|
|
2359
|
+
let kept = 0;
|
|
2360
|
+
for (let index = 0; index < survivors; index += 1) {
|
|
2361
|
+
const row = selection[index] ?? 0;
|
|
2362
|
+
const sourceRow = rows?.[row] ?? -1;
|
|
2363
|
+
if (sourceRow < 0 || sourceRow >= vectorLength)
|
|
2364
|
+
continue;
|
|
2365
|
+
const slot = sourceRow - windowStart;
|
|
2366
|
+
if (slot < 0 || slot >= slots) {
|
|
2367
|
+
throw new RangeError("Streamed vector row is outside the resident window");
|
|
2368
|
+
}
|
|
2369
|
+
if (((validity[slot >>> 3] ?? 0) & (1 << (slot & 7))) === 0)
|
|
2370
|
+
continue;
|
|
2371
|
+
const code = codes[slot] ?? NULL_STRING_CODE;
|
|
2372
|
+
if (code === NULL_STRING_CODE || matches[code] !== 1)
|
|
2373
|
+
continue;
|
|
2374
|
+
selection[kept] = row;
|
|
2375
|
+
kept += 1;
|
|
2376
|
+
}
|
|
2377
|
+
return kept;
|
|
2378
|
+
}
|
|
2267
2379
|
// The per-batch selection scratch: batches are bounded by DEFAULT_BATCH_ROWS, spills may use
|
|
2268
2380
|
// larger pages, so the scratch grows to the largest batch seen and is trivially small.
|
|
2269
2381
|
let selectionScratch = new Uint32Array(DEFAULT_BATCH_ROWS);
|
|
@@ -2284,6 +2396,9 @@ function applyPredicateKernel(plan, predicate, batch, selection, survivors) {
|
|
|
2284
2396
|
if (predicate.dictionaryLike !== undefined) {
|
|
2285
2397
|
return filterDictionaryLike(predicate.dictionaryLike, batch, selection, survivors);
|
|
2286
2398
|
}
|
|
2399
|
+
if (predicate.dictionaryNumeric !== undefined) {
|
|
2400
|
+
return filterDictionaryNumeric(predicate.dictionaryNumeric, batch, selection, survivors);
|
|
2401
|
+
}
|
|
2287
2402
|
if (predicate.disjunction !== undefined) {
|
|
2288
2403
|
return filterDisjunction(plan, predicate.disjunction, batch, selection, survivors);
|
|
2289
2404
|
}
|
|
@@ -3806,7 +3921,7 @@ function evaluateFinalExpression(plan, expression, group) {
|
|
|
3806
3921
|
if (count === 0)
|
|
3807
3922
|
return null;
|
|
3808
3923
|
if (expression.name === "JSON_ARRAYAGG") {
|
|
3809
|
-
return
|
|
3924
|
+
return preservedJsonDomainValue(jsonConstructor("JSON_ARRAY", required(group.lists, "JSON aggregate list state is missing")[aggregateIndex] ?? []));
|
|
3810
3925
|
}
|
|
3811
3926
|
if (expression.name === "STRING_AGG") {
|
|
3812
3927
|
const members = required(group.lists, "STRING_AGG list state is missing")[aggregateIndex] ?? [];
|
|
@@ -3877,12 +3992,14 @@ function projectBatchRow(plan, batch, row) {
|
|
|
3877
3992
|
}
|
|
3878
3993
|
return result;
|
|
3879
3994
|
}
|
|
3880
|
-
const multiple = plan.sourceTables.length > 1;
|
|
3995
|
+
const multiple = plan.sourceTables.filter((table) => [...table.columns.keys()].some((name) => !name.startsWith("\0"))).length > 1;
|
|
3881
3996
|
for (let source = 0; source < plan.sourceTables.length; source += 1) {
|
|
3882
3997
|
const table = required(plan.sourceTables[source], "Wildcard source table is missing");
|
|
3883
3998
|
const rowIndex = batch.rowsBySource[source]?.[row] ?? -1;
|
|
3884
3999
|
const prefix = multiple ? `${plan.sourceAliases[source] ?? ""}.` : "";
|
|
3885
4000
|
for (const [name, vector] of table.columns) {
|
|
4001
|
+
if (name.startsWith("\0"))
|
|
4002
|
+
continue;
|
|
3886
4003
|
const outputName = multiple ? prefix + name : name;
|
|
3887
4004
|
const value = vectorValue(vector, rowIndex);
|
|
3888
4005
|
if (outputName === "__proto__")
|
|
@@ -3894,8 +4011,10 @@ function projectBatchRow(plan, batch, row) {
|
|
|
3894
4011
|
return result;
|
|
3895
4012
|
}
|
|
3896
4013
|
function wildcardColumnNames(plan) {
|
|
3897
|
-
const multiple = plan.sourceTables.length > 1;
|
|
3898
|
-
return plan.sourceTables.flatMap((table, source) => [...table.columns.keys()]
|
|
4014
|
+
const multiple = plan.sourceTables.filter((table) => [...table.columns.keys()].some((name) => !name.startsWith("\0"))).length > 1;
|
|
4015
|
+
return plan.sourceTables.flatMap((table, source) => [...table.columns.keys()]
|
|
4016
|
+
.filter((name) => !name.startsWith("\0"))
|
|
4017
|
+
.map((name) => (multiple ? `${plan.sourceAliases[source] ?? ""}.${name}` : name)));
|
|
3899
4018
|
}
|
|
3900
4019
|
/** Detects `stringColumn = 'literal'` (or !=) so batches compare dictionary codes per row. */
|
|
3901
4020
|
function detectDictionaryEquality(predicate) {
|
|
@@ -4107,6 +4226,143 @@ function dictionaryLikeMatches(dictionary, pattern, caseInsensitive, escape) {
|
|
|
4107
4226
|
}
|
|
4108
4227
|
return matches;
|
|
4109
4228
|
}
|
|
4229
|
+
const exactNumericDictionaryCache = new WeakMap();
|
|
4230
|
+
const dictionaryNumericCache = new WeakMap();
|
|
4231
|
+
function isExactNumericDictionary(dictionary) {
|
|
4232
|
+
const cached = exactNumericDictionaryCache.get(dictionary);
|
|
4233
|
+
if (cached !== undefined)
|
|
4234
|
+
return cached;
|
|
4235
|
+
const exact = dictionary.every((value) => isExactNumeric(value));
|
|
4236
|
+
exactNumericDictionaryCache.set(dictionary, exact);
|
|
4237
|
+
return exact;
|
|
4238
|
+
}
|
|
4239
|
+
function dictionaryNumericMatches(comparison, dictionary) {
|
|
4240
|
+
// An ordinary TEXT dictionary must retain the generic evaluator, including its error and
|
|
4241
|
+
// coercion behavior. Physical NUMERIC dictionaries contain only validated internal tags.
|
|
4242
|
+
if (!isExactNumericDictionary(dictionary))
|
|
4243
|
+
return undefined;
|
|
4244
|
+
let comparisons = dictionaryNumericCache.get(dictionary);
|
|
4245
|
+
if (comparisons === undefined) {
|
|
4246
|
+
comparisons = new Map();
|
|
4247
|
+
dictionaryNumericCache.set(dictionary, comparisons);
|
|
4248
|
+
}
|
|
4249
|
+
let matches = comparisons.get(comparison.signature);
|
|
4250
|
+
if (matches === undefined) {
|
|
4251
|
+
matches = new Uint8Array(dictionary.length);
|
|
4252
|
+
for (let code = 0; code < dictionary.length; code += 1) {
|
|
4253
|
+
matches[code] = comparisonValue(comparison.operator, comparison.expression.evaluate(dictionary[code] ?? ""), comparison.target)
|
|
4254
|
+
? 1
|
|
4255
|
+
: 0;
|
|
4256
|
+
}
|
|
4257
|
+
// The dictionary owns this cache weakly. Bound the number of query shapes retained by one
|
|
4258
|
+
// high-cardinality NUMERIC column, matching the dictionary LIKE cache above.
|
|
4259
|
+
if (comparisons.size >= 32)
|
|
4260
|
+
comparisons.clear();
|
|
4261
|
+
comparisons.set(comparison.signature, matches);
|
|
4262
|
+
}
|
|
4263
|
+
return matches;
|
|
4264
|
+
}
|
|
4265
|
+
function constantBoundExpressionValue(expression) {
|
|
4266
|
+
if (expression.kind === "literal")
|
|
4267
|
+
return { value: expression.value };
|
|
4268
|
+
if (expression.kind === "binary") {
|
|
4269
|
+
const left = constantBoundExpressionValue(expression.left);
|
|
4270
|
+
const right = constantBoundExpressionValue(expression.right);
|
|
4271
|
+
if (left === undefined || right === undefined)
|
|
4272
|
+
return undefined;
|
|
4273
|
+
try {
|
|
4274
|
+
return { value: binaryValue(expression.operator, left.value, right.value) };
|
|
4275
|
+
}
|
|
4276
|
+
catch {
|
|
4277
|
+
return undefined;
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
// CAST is the only scalar wrapper needed by exact-NUMERIC arithmetic. Keeping the constant
|
|
4281
|
+
// folder deliberately narrow avoids changing error timing for general scalar expressions.
|
|
4282
|
+
if (expression.kind !== "call" || expression.name !== "CAST")
|
|
4283
|
+
return undefined;
|
|
4284
|
+
const values = [];
|
|
4285
|
+
for (const argument of expression.arguments) {
|
|
4286
|
+
const constant = constantBoundExpressionValue(argument);
|
|
4287
|
+
if (constant === undefined)
|
|
4288
|
+
return undefined;
|
|
4289
|
+
values.push(constant.value);
|
|
4290
|
+
}
|
|
4291
|
+
try {
|
|
4292
|
+
return { value: scalarFunctionValue(expression.name, values) };
|
|
4293
|
+
}
|
|
4294
|
+
catch {
|
|
4295
|
+
return undefined;
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
function numericComparisonConstant(expression) {
|
|
4299
|
+
const constant = constantBoundExpressionValue(expression)?.value;
|
|
4300
|
+
if (typeof constant === "number" && Number.isFinite(constant))
|
|
4301
|
+
return constant;
|
|
4302
|
+
return isExactNumeric(constant) ? constant : undefined;
|
|
4303
|
+
}
|
|
4304
|
+
function dictionaryNumericExpression(expression) {
|
|
4305
|
+
if (expression.kind === "column" && expression.vector.kind === "string") {
|
|
4306
|
+
return {
|
|
4307
|
+
source: expression.source,
|
|
4308
|
+
vector: expression.vector,
|
|
4309
|
+
signature: expression.signature,
|
|
4310
|
+
evaluate: (value) => value,
|
|
4311
|
+
};
|
|
4312
|
+
}
|
|
4313
|
+
if (expression.kind !== "binary")
|
|
4314
|
+
return undefined;
|
|
4315
|
+
const sides = [
|
|
4316
|
+
{ column: expression.left, constant: expression.right, columnFirst: true },
|
|
4317
|
+
{ column: expression.right, constant: expression.left, columnFirst: false },
|
|
4318
|
+
];
|
|
4319
|
+
for (const { column, constant, columnFirst } of sides) {
|
|
4320
|
+
if (column.kind !== "column" || column.vector.kind !== "string")
|
|
4321
|
+
continue;
|
|
4322
|
+
const value = numericComparisonConstant(constant);
|
|
4323
|
+
if (value === undefined)
|
|
4324
|
+
continue;
|
|
4325
|
+
return {
|
|
4326
|
+
source: column.source,
|
|
4327
|
+
vector: column.vector,
|
|
4328
|
+
signature: expression.signature,
|
|
4329
|
+
evaluate: columnFirst
|
|
4330
|
+
? (dictionaryValue) => binaryValue(expression.operator, dictionaryValue, value)
|
|
4331
|
+
: (dictionaryValue) => binaryValue(expression.operator, value, dictionaryValue),
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
return undefined;
|
|
4335
|
+
}
|
|
4336
|
+
/** Detects exact-NUMERIC dictionary arithmetic compared with a constant. */
|
|
4337
|
+
function detectDictionaryNumericComparison(predicate) {
|
|
4338
|
+
if (!primitiveOperators.has(predicate.operator))
|
|
4339
|
+
return undefined;
|
|
4340
|
+
const comparison = predicate.operator;
|
|
4341
|
+
const sides = [
|
|
4342
|
+
{ expression: predicate.left, target: predicate.right, operator: comparison },
|
|
4343
|
+
{
|
|
4344
|
+
expression: predicate.right,
|
|
4345
|
+
target: predicate.left,
|
|
4346
|
+
operator: reverseComparisonOperator(comparison),
|
|
4347
|
+
},
|
|
4348
|
+
];
|
|
4349
|
+
for (const { expression, target, operator } of sides) {
|
|
4350
|
+
const numeric = dictionaryNumericExpression(expression);
|
|
4351
|
+
if (numeric === undefined)
|
|
4352
|
+
continue;
|
|
4353
|
+
const value = numericComparisonConstant(target);
|
|
4354
|
+
if (value === undefined)
|
|
4355
|
+
continue;
|
|
4356
|
+
return {
|
|
4357
|
+
expression: numeric,
|
|
4358
|
+
operator,
|
|
4359
|
+
target: value,
|
|
4360
|
+
signature: `${numeric.signature}\u0001${operator}\u0001${target.signature}`,
|
|
4361
|
+
cache: { dictionary: undefined, matches: new Uint8Array(0) },
|
|
4362
|
+
};
|
|
4363
|
+
}
|
|
4364
|
+
return undefined;
|
|
4365
|
+
}
|
|
4110
4366
|
function stringCodeAt(vector, rowIndex) {
|
|
4111
4367
|
if (rowIndex < 0 || rowIndex >= vector.length)
|
|
4112
4368
|
return undefined;
|
|
@@ -4290,6 +4546,21 @@ function evaluateBatchPredicate(plan, predicate, batch, row) {
|
|
|
4290
4546
|
const matched = like.cache.matches[code] === 1;
|
|
4291
4547
|
return like.negated ? !matched : matched;
|
|
4292
4548
|
}
|
|
4549
|
+
const dictionaryNumeric = predicate.dictionaryNumeric;
|
|
4550
|
+
if (dictionaryNumeric !== undefined) {
|
|
4551
|
+
const vector = dictionaryNumeric.expression.vector;
|
|
4552
|
+
if (dictionaryNumeric.cache.dictionary !== vector.dictionary) {
|
|
4553
|
+
const matches = dictionaryNumericMatches(dictionaryNumeric, vector.dictionary);
|
|
4554
|
+
if (matches !== undefined) {
|
|
4555
|
+
dictionaryNumeric.cache.dictionary = vector.dictionary;
|
|
4556
|
+
dictionaryNumeric.cache.matches = matches;
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
if (dictionaryNumeric.cache.dictionary === vector.dictionary) {
|
|
4560
|
+
const code = stringCodeAt(vector, batch.rowsBySource[dictionaryNumeric.expression.source]?.[row] ?? -1);
|
|
4561
|
+
return code !== undefined && dictionaryNumeric.cache.matches[code] === 1;
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4293
4564
|
if (predicate.operator === "IS TRUE" ||
|
|
4294
4565
|
predicate.operator === "LIKE" ||
|
|
4295
4566
|
predicate.operator === "NOT LIKE" ||
|
|
@@ -4506,7 +4777,13 @@ function comparisonValue(operator, leftValue, rightValue) {
|
|
|
4506
4777
|
return comparison <= 0;
|
|
4507
4778
|
}
|
|
4508
4779
|
function comparable(value) {
|
|
4509
|
-
|
|
4780
|
+
if (value instanceof Date)
|
|
4781
|
+
return dateMilliseconds(value);
|
|
4782
|
+
if (isDateDomainValue(value)) {
|
|
4783
|
+
const external = externalSqlDomainValue(value);
|
|
4784
|
+
return typeof external === "string" ? Date.parse(`${external}T00:00:00.000Z`) : value;
|
|
4785
|
+
}
|
|
4786
|
+
return value;
|
|
4510
4787
|
}
|
|
4511
4788
|
function groupKey(value) {
|
|
4512
4789
|
const comparableValue = comparable(value);
|
|
@@ -4547,35 +4824,7 @@ function stableSortRows(rows, orderBy) {
|
|
|
4547
4824
|
}
|
|
4548
4825
|
}
|
|
4549
4826
|
function compareValues(left, right) {
|
|
4550
|
-
|
|
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;
|
|
4561
|
-
if (a === b)
|
|
4562
|
-
return 0;
|
|
4563
|
-
if (a === null || a === undefined)
|
|
4564
|
-
return -1;
|
|
4565
|
-
if (b === null || b === undefined)
|
|
4566
|
-
return 1;
|
|
4567
|
-
if (typeof a === "number" && typeof b === "number") {
|
|
4568
|
-
if (Number.isNaN(a))
|
|
4569
|
-
return Number.isNaN(b) ? 0 : 1;
|
|
4570
|
-
if (Number.isNaN(b))
|
|
4571
|
-
return -1;
|
|
4572
|
-
return a - b;
|
|
4573
|
-
}
|
|
4574
|
-
if (typeof a === "string" && typeof b === "string")
|
|
4575
|
-
return compareSqlStrings(a, b);
|
|
4576
|
-
if (typeof a === "boolean" && typeof b === "boolean")
|
|
4577
|
-
return Number(a) - Number(b);
|
|
4578
|
-
throw new TypeError("Values must have comparable SQL types");
|
|
4827
|
+
return compareSqlValues(left, right);
|
|
4579
4828
|
}
|
|
4580
4829
|
function numeric(value) {
|
|
4581
4830
|
if (typeof value !== "number")
|