@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.
Files changed (47) hide show
  1. package/README.md +5 -5
  2. package/dist/engine/cancellation.d.ts +2 -0
  3. package/dist/engine/cancellation.js +4 -0
  4. package/dist/engine/catalog.d.ts +5 -1
  5. package/dist/engine/catalog.js +5 -1
  6. package/dist/engine/client.d.ts +34 -6
  7. package/dist/engine/client.js +87 -19
  8. package/dist/engine/database.d.ts +43 -20
  9. package/dist/engine/database.js +823 -164
  10. package/dist/engine/defaults.js +11 -0
  11. package/dist/engine/errors.d.ts +19 -0
  12. package/dist/engine/errors.js +31 -0
  13. package/dist/engine/fts.d.ts +2 -15
  14. package/dist/engine/live.d.ts +1 -7
  15. package/dist/engine/live.js +12 -13
  16. package/dist/engine/optimizer.js +546 -39
  17. package/dist/engine/query-cache.js +1 -0
  18. package/dist/engine/query.d.ts +16 -278
  19. package/dist/engine/query.js +260 -74
  20. package/dist/engine/result-wire.d.ts +2 -0
  21. package/dist/engine/result-wire.js +21 -5
  22. package/dist/engine/schema-wire.d.ts +14 -1
  23. package/dist/engine/schema-wire.js +7 -1
  24. package/dist/engine/schema.d.ts +83 -32
  25. package/dist/engine/schema.js +180 -14
  26. package/dist/engine/sql-domains.d.ts +11 -0
  27. package/dist/engine/sql-domains.js +65 -1
  28. package/dist/engine/sql-json.js +22 -3
  29. package/dist/engine/sql-semantics.js +21 -3
  30. package/dist/engine/vector.d.ts +2 -2
  31. package/dist/engine/vector.js +328 -79
  32. package/dist/engine/worker-host.js +119 -44
  33. package/dist/plan/index.d.ts +5 -4
  34. package/dist/plan/index.js +3 -3
  35. package/dist/plan/model.d.ts +218 -0
  36. package/dist/plan/model.js +1 -0
  37. package/dist/storage/indexeddb.js +4 -12
  38. package/dist/storage/toolkit/record-core.js +7 -22
  39. package/dist/storage/types.d.ts +26 -8
  40. package/dist/storage/types.js +85 -0
  41. package/dist/transactions/index.d.ts +5 -3
  42. package/dist/transactions/index.js +58 -8
  43. package/dist/worker-protocol/index.d.ts +6 -1
  44. package/dist/worker-protocol/index.js +5 -2
  45. package/package.json +1 -1
  46. package/postgres-feature-profile.json +5 -0
  47. package/sql-feature-matrix.json +75 -19
@@ -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 { cachedListMembership, childExpressions, distinctFromComparison, nullOrder, isScalarFunctionName, likeMatches, orderOutputName, parseQuantified, quantifiedComparison, scalarFunctionValue, } from "./query.js";
4
- import { jsonValueOf } from "./sql-json.js";
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 { compareSqlStrings, compileSimilarPattern, defineSqlResultProperty, } from "./sql-semantics.js";
10
- import { exactNumericBinary, exactNumericCompare, collatedDomainCompare, enumDomainCompare, externalSqlDomainValue, isExactNumeric, protectedSqlTextValue, } from "./sql-domains.js";
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
- return this.execute();
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 TypeError(`Unknown table: ${source.table}`);
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 || bound.dictionaryLike !== 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.map((source, index) => ({
655
- alias: source.alias,
656
- columns: [...(sourceTables[index]?.columns.keys() ?? [])],
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?.throwIfAborted();
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?.throwIfAborted();
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 output = new ResultSink(scanPlan, pageMemory, options.loadScanWindow === undefined);
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({ columns: [...columns], rows });
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 JSON.stringify((required(group.lists, "JSON aggregate list state is missing")[aggregateIndex] ?? []).map(jsonValueOf));
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()].map((name) => multiple ? `${plan.sourceAliases[source] ?? ""}.${name}` : name));
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
- return value instanceof Date ? dateMilliseconds(value) : value;
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
- 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;
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")