@minnowdb/core 0.2.0 → 0.3.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 (146) hide show
  1. package/README.md +21 -32
  2. package/dist/engine/artifact-cache.d.ts +2 -0
  3. package/dist/engine/artifact-cache.d.ts.map +1 -1
  4. package/dist/engine/artifact-cache.js +5 -0
  5. package/dist/engine/artifact-cache.js.map +1 -1
  6. package/dist/engine/batch.d.ts +6 -1
  7. package/dist/engine/batch.d.ts.map +1 -1
  8. package/dist/engine/batch.js +17 -6
  9. package/dist/engine/batch.js.map +1 -1
  10. package/dist/engine/catalog.d.ts +22 -2
  11. package/dist/engine/catalog.d.ts.map +1 -1
  12. package/dist/engine/catalog.js +30 -3
  13. package/dist/engine/catalog.js.map +1 -1
  14. package/dist/engine/client.d.ts +3 -0
  15. package/dist/engine/client.d.ts.map +1 -1
  16. package/dist/engine/client.js +21 -10
  17. package/dist/engine/client.js.map +1 -1
  18. package/dist/engine/database.d.ts +71 -5
  19. package/dist/engine/database.d.ts.map +1 -1
  20. package/dist/engine/database.js +3619 -421
  21. package/dist/engine/database.js.map +1 -1
  22. package/dist/engine/defaults.d.ts +4 -7
  23. package/dist/engine/defaults.d.ts.map +1 -1
  24. package/dist/engine/defaults.js +47 -21
  25. package/dist/engine/defaults.js.map +1 -1
  26. package/dist/engine/fts.d.ts.map +1 -1
  27. package/dist/engine/fts.js +2 -0
  28. package/dist/engine/fts.js.map +1 -1
  29. package/dist/engine/index.d.ts +1 -0
  30. package/dist/engine/index.d.ts.map +1 -1
  31. package/dist/engine/index.js +1 -0
  32. package/dist/engine/index.js.map +1 -1
  33. package/dist/engine/live.d.ts +6 -2
  34. package/dist/engine/live.d.ts.map +1 -1
  35. package/dist/engine/live.js +10 -2
  36. package/dist/engine/live.js.map +1 -1
  37. package/dist/engine/optimizer.d.ts.map +1 -1
  38. package/dist/engine/optimizer.js +258 -23
  39. package/dist/engine/optimizer.js.map +1 -1
  40. package/dist/engine/query-cache.d.ts +1 -1
  41. package/dist/engine/query-cache.d.ts.map +1 -1
  42. package/dist/engine/query-cache.js +3 -1
  43. package/dist/engine/query-cache.js.map +1 -1
  44. package/dist/engine/query.d.ts +132 -22
  45. package/dist/engine/query.d.ts.map +1 -1
  46. package/dist/engine/query.js +1368 -246
  47. package/dist/engine/query.js.map +1 -1
  48. package/dist/engine/schema-wire.d.ts +6 -2
  49. package/dist/engine/schema-wire.d.ts.map +1 -1
  50. package/dist/engine/schema-wire.js +40 -33
  51. package/dist/engine/schema-wire.js.map +1 -1
  52. package/dist/engine/schema.d.ts +157 -76
  53. package/dist/engine/schema.d.ts.map +1 -1
  54. package/dist/engine/schema.js +548 -103
  55. package/dist/engine/schema.js.map +1 -1
  56. package/dist/engine/sort-keys.d.ts +4 -3
  57. package/dist/engine/sort-keys.d.ts.map +1 -1
  58. package/dist/engine/sort-keys.js +42 -27
  59. package/dist/engine/sort-keys.js.map +1 -1
  60. package/dist/engine/sql-domains.d.ts +26 -0
  61. package/dist/engine/sql-domains.d.ts.map +1 -0
  62. package/dist/engine/sql-domains.js +421 -0
  63. package/dist/engine/sql-domains.js.map +1 -0
  64. package/dist/engine/sql-driver.d.ts +19 -0
  65. package/dist/engine/sql-driver.d.ts.map +1 -0
  66. package/dist/engine/sql-driver.js +2 -0
  67. package/dist/engine/sql-driver.js.map +1 -0
  68. package/dist/engine/sql-json.d.ts +7 -8
  69. package/dist/engine/sql-json.d.ts.map +1 -1
  70. package/dist/engine/sql-json.js +28 -14
  71. package/dist/engine/sql-json.js.map +1 -1
  72. package/dist/engine/sql-semantics.d.ts +2 -0
  73. package/dist/engine/sql-semantics.d.ts.map +1 -1
  74. package/dist/engine/sql-semantics.js +69 -3
  75. package/dist/engine/sql-semantics.js.map +1 -1
  76. package/dist/engine/vector.d.ts +5 -1
  77. package/dist/engine/vector.d.ts.map +1 -1
  78. package/dist/engine/vector.js +433 -61
  79. package/dist/engine/vector.js.map +1 -1
  80. package/dist/engine/worker-host.d.ts +1 -0
  81. package/dist/engine/worker-host.d.ts.map +1 -1
  82. package/dist/engine/worker-host.js +8 -0
  83. package/dist/engine/worker-host.js.map +1 -1
  84. package/dist/plan/index.d.ts +3 -3
  85. package/dist/plan/index.js +3 -3
  86. package/dist/storage/indexeddb.d.ts +44 -5
  87. package/dist/storage/indexeddb.d.ts.map +1 -1
  88. package/dist/storage/indexeddb.js +738 -175
  89. package/dist/storage/indexeddb.js.map +1 -1
  90. package/dist/storage/memory.d.ts +27 -10
  91. package/dist/storage/memory.d.ts.map +1 -1
  92. package/dist/storage/memory.js +67 -18
  93. package/dist/storage/memory.js.map +1 -1
  94. package/dist/storage/opfs/leader.d.ts +28 -6
  95. package/dist/storage/opfs/leader.d.ts.map +1 -1
  96. package/dist/storage/opfs/leader.js +301 -34
  97. package/dist/storage/opfs/leader.js.map +1 -1
  98. package/dist/storage/opfs/rpc.d.ts.map +1 -1
  99. package/dist/storage/opfs/rpc.js +2 -1
  100. package/dist/storage/opfs/rpc.js.map +1 -1
  101. package/dist/storage/opfs/store.d.ts +34 -5
  102. package/dist/storage/opfs/store.d.ts.map +1 -1
  103. package/dist/storage/opfs/store.js +41 -0
  104. package/dist/storage/opfs/store.js.map +1 -1
  105. package/dist/storage/snapshot.d.ts +14 -0
  106. package/dist/storage/snapshot.d.ts.map +1 -1
  107. package/dist/storage/snapshot.js +44 -0
  108. package/dist/storage/snapshot.js.map +1 -1
  109. package/dist/storage/toolkit/index.d.ts +1 -1
  110. package/dist/storage/toolkit/index.d.ts.map +1 -1
  111. package/dist/storage/toolkit/index.js +1 -1
  112. package/dist/storage/toolkit/index.js.map +1 -1
  113. package/dist/storage/toolkit/record-core.d.ts +19 -5
  114. package/dist/storage/toolkit/record-core.d.ts.map +1 -1
  115. package/dist/storage/toolkit/record-core.js +353 -78
  116. package/dist/storage/toolkit/record-core.js.map +1 -1
  117. package/dist/storage/toolkit/wire.d.ts +15 -0
  118. package/dist/storage/toolkit/wire.d.ts.map +1 -1
  119. package/dist/storage/toolkit/wire.js +134 -0
  120. package/dist/storage/toolkit/wire.js.map +1 -1
  121. package/dist/storage/types.d.ts +213 -31
  122. package/dist/storage/types.d.ts.map +1 -1
  123. package/dist/storage/types.js +291 -20
  124. package/dist/storage/types.js.map +1 -1
  125. package/dist/testing/block-store-conformance.d.ts.map +1 -1
  126. package/dist/testing/block-store-conformance.js +153 -2
  127. package/dist/testing/block-store-conformance.js.map +1 -1
  128. package/dist/testing/index.d.ts +9 -0
  129. package/dist/testing/index.d.ts.map +1 -1
  130. package/dist/testing/index.js +23 -0
  131. package/dist/testing/index.js.map +1 -1
  132. package/dist/testing/simulator.d.ts +98 -0
  133. package/dist/testing/simulator.d.ts.map +1 -0
  134. package/dist/testing/simulator.js +560 -0
  135. package/dist/testing/simulator.js.map +1 -0
  136. package/dist/testing/sqllogictest.d.ts +90 -0
  137. package/dist/testing/sqllogictest.d.ts.map +1 -0
  138. package/dist/testing/sqllogictest.js +435 -0
  139. package/dist/testing/sqllogictest.js.map +1 -0
  140. package/dist/transactions/index.d.ts +18 -3
  141. package/dist/transactions/index.d.ts.map +1 -1
  142. package/dist/transactions/index.js +64 -8
  143. package/dist/transactions/index.js.map +1 -1
  144. package/package.json +5 -3
  145. package/postgres-feature-profile.json +223 -0
  146. package/sql-feature-matrix.json +179 -251
@@ -1,9 +1,11 @@
1
- import { cachedListMembership, distinctFromComparison, explicitNullOrder, isScalarFunctionName, likeMatches, orderOutputName, parseQuantified, quantifiedComparison, scalarFunctionValue, } from "./query.js";
1
+ import { cachedListMembership, childExpressions, distinctFromComparison, nullOrder, isScalarFunctionName, likeMatches, orderOutputName, parseQuantified, quantifiedComparison, scalarFunctionValue, } from "./query.js";
2
+ import { jsonValueOf } from "./sql-json.js";
2
3
  import { bm25DocumentScore, cachedQueryTerms, FtsStatsAccumulator, fullTermsMask, renderDocumentValue, termFrequencies, termsMask, tokenize, } from "./fts.js";
3
4
  import { ByteGroupIndex } from "./group-index.js";
4
5
  import { ByteJoinIndex } from "./join-index.js";
5
6
  import { QueryMemoryBudgetError, QueryMemoryContext, } from "./memory.js";
6
- import { compareSqlStrings, defineSqlResultProperty } from "./sql-semantics.js";
7
+ import { compareSqlStrings, compileSimilarPattern, defineSqlResultProperty, } from "./sql-semantics.js";
8
+ import { exactNumericBinary, exactNumericCompare, collatedDomainCompare, enumDomainCompare, externalSqlDomainValue, isExactNumeric, protectedSqlTextValue, } from "./sql-domains.js";
7
9
  import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
8
10
  const DEFAULT_BATCH_ROWS = 2_048;
9
11
  /** Above this, locating each IN member separately costs more than scanning between them. */
@@ -59,7 +61,7 @@ export function createColumnarTable(name, columns, uniqueKey) {
59
61
  ...(uniqueKey === undefined ? {} : { uniqueKey }),
60
62
  };
61
63
  }
62
- export function columnarTableFromRows(name, rows, projectedColumnNames) {
64
+ export function columnarTableFromRows(name, rows, projectedColumnNames, protectText = true) {
63
65
  const columnNameSet = new Set(projectedColumnNames);
64
66
  if (projectedColumnNames === undefined) {
65
67
  for (const row of rows) {
@@ -72,7 +74,10 @@ export function columnarTableFromRows(name, rows, projectedColumnNames) {
72
74
  return { name, rowCount: rows.length, columns: new Map() };
73
75
  const columns = new Map();
74
76
  for (const columnName of columnNames) {
75
- const values = rows.map((row) => row[columnName] ?? null);
77
+ const values = rows.map((row) => {
78
+ const value = row[columnName] ?? null;
79
+ return protectText && typeof value === "string" ? protectedSqlTextValue(value) : value;
80
+ });
76
81
  columns.set(columnName, { type: inferVectorType(values), values });
77
82
  }
78
83
  return createColumnarTable(name, columns);
@@ -105,7 +110,7 @@ export function prepareVectorQuery(plan, inputTables, options = {}) {
105
110
  async executeAsync(executionOptions = {}) {
106
111
  if (closed)
107
112
  throw new Error("Prepared vector query is closed");
108
- const canSpillSort = bound.orderBy.length > 0 && !bound.grouped;
113
+ const canSpillSort = bound.orderBy.length > 0 && !bound.grouped && !bound.sourceOrdered;
109
114
  // An unordered grouped plan spills too: the empty ordering makes the pairwise merge a
110
115
  // stable concatenation, and partition-wise accumulation bounds peak group state.
111
116
  const canSpillHash = bound.grouped && bound.groupBy.length > 0;
@@ -255,7 +260,7 @@ export function vectorValue(vector, rowIndex) {
255
260
  return code === NULL_STRING_CODE ? null : (vector.dictionary[code] ?? null);
256
261
  }
257
262
  function columnarTablePayloadBytes(table) {
258
- let total = 0;
263
+ let total = table.scanOrder?.byteLength ?? 0;
259
264
  for (const vector of table.columns.values()) {
260
265
  total = safeMemorySum(total, vector.validity.byteLength, "Column vector payload");
261
266
  if (vector.kind === "string") {
@@ -315,7 +320,178 @@ function disjunctiveNormalForm(predicate) {
315
320
  };
316
321
  return visit(predicate.left) && branches.length > 1 ? branches : undefined;
317
322
  }
323
+ /**
324
+ * Gives a pure comma/CROSS-join group a connected physical order once schemas can resolve bare
325
+ * column names. SQLLogicTest deliberately permutes FROM lists; preserving that written order can
326
+ * otherwise materialize several disconnected Cartesian products before their WHERE equalities
327
+ * become applicable. The streamed base stays fixed; explicit/outer joins and wildcard projection
328
+ * keep their complete written order.
329
+ *
330
+ * One equality that connects each new source becomes the inner hash-join key. The remaining
331
+ * conditions stay ordinary predicates, so null and residual-filter semantics do not change.
332
+ */
333
+ function orderCartesianJoins(plan, tables) {
334
+ if (plan.joins.length < 2 ||
335
+ plan.select.some((item) => item.expression.kind === "wildcard") ||
336
+ plan.joins.some((join) => !isCartesianJoin(join))) {
337
+ return plan;
338
+ }
339
+ const entries = [
340
+ { source: plan.base, originalIndex: 0 },
341
+ ...plan.joins.map((join, index) => ({
342
+ source: tableSourceOf(join),
343
+ originalIndex: index + 1,
344
+ })),
345
+ ];
346
+ const sourceTables = entries.map(({ source }) => tables.get(source.table));
347
+ if (sourceTables.some((table) => table === undefined))
348
+ return plan;
349
+ const predicateSources = plan.predicates.map((predicate) => ({
350
+ predicate,
351
+ sources: rawPredicateSources(predicate, entries, sourceTables),
352
+ }));
353
+ // The preparation layer may stream the written base table through a resident window. Keep
354
+ // that source fixed and order only the fully materialized join sides around it.
355
+ const remaining = new Set(entries.slice(1).map(({ originalIndex }) => originalIndex));
356
+ const ordered = [0];
357
+ while (remaining.size > 0) {
358
+ let best;
359
+ let bestScore = Number.NEGATIVE_INFINITY;
360
+ for (const candidate of remaining) {
361
+ const score = cartesianSourceScore(candidate, ordered, predicateSources);
362
+ if (score > bestScore || (score === bestScore && (best === undefined || candidate < best))) {
363
+ best = candidate;
364
+ bestScore = score;
365
+ }
366
+ }
367
+ if (best === undefined)
368
+ return plan;
369
+ ordered.push(best);
370
+ remaining.delete(best);
371
+ }
372
+ const predicates = [...plan.predicates];
373
+ const available = new Set([ordered[0] ?? 0]);
374
+ const joins = [];
375
+ for (const sourceIndex of ordered.slice(1)) {
376
+ const source = entries[sourceIndex]?.source;
377
+ if (source === undefined)
378
+ return plan;
379
+ const keyIndex = predicates.findIndex((predicate) => predicateJoinsSource(predicate, sourceIndex, available, entries, sourceTables));
380
+ const key = keyIndex < 0 ? undefined : predicates.splice(keyIndex, 1)[0];
381
+ joins.push(key === undefined ? cartesianJoin(source) : keyedInnerJoin(source, key));
382
+ available.add(sourceIndex);
383
+ }
384
+ return { ...plan, base: plan.base, joins, predicates };
385
+ }
386
+ function isCartesianJoin(join) {
387
+ const condition = join.on;
388
+ return (join.kind === "inner" &&
389
+ condition?.kind === "condition" &&
390
+ condition.operator === "=" &&
391
+ condition.left.kind === "literal" &&
392
+ condition.left.value === 1 &&
393
+ condition.right.kind === "literal" &&
394
+ condition.right.value === 1);
395
+ }
396
+ function tableSourceOf(join) {
397
+ const { kind, left, right, on, full, natural, ...source } = join;
398
+ void kind;
399
+ void left;
400
+ void right;
401
+ void on;
402
+ void full;
403
+ void natural;
404
+ return source;
405
+ }
406
+ function cartesianJoin(source) {
407
+ return {
408
+ ...source,
409
+ kind: "inner",
410
+ left: { kind: "literal", value: null },
411
+ right: { kind: "literal", value: null },
412
+ on: {
413
+ kind: "condition",
414
+ operator: "=",
415
+ left: { kind: "literal", value: 1 },
416
+ right: { kind: "literal", value: 1 },
417
+ },
418
+ };
419
+ }
420
+ function keyedInnerJoin(source, predicate) {
421
+ return {
422
+ ...source,
423
+ kind: "inner",
424
+ left: predicate.left,
425
+ right: predicate.right,
426
+ };
427
+ }
428
+ function cartesianSourceScore(candidate, ordered, predicates) {
429
+ const available = new Set(ordered);
430
+ let local = 0;
431
+ let connected = 0;
432
+ let equality = 0;
433
+ for (const entry of predicates) {
434
+ const sources = entry.sources;
435
+ if (sources?.has(candidate) !== true)
436
+ continue;
437
+ if (sources.size === 1) {
438
+ local++;
439
+ continue;
440
+ }
441
+ if ([...sources].every((source) => source === candidate || available.has(source))) {
442
+ connected++;
443
+ if (entry.predicate.operator === "=")
444
+ equality++;
445
+ }
446
+ }
447
+ // A usable equality dominates a general connection, which dominates a local filter; original
448
+ // order breaks ties. The empty-order branch keeps the helper correct if base selection is ever
449
+ // made schema-aware too, though the streaming executor currently fixes that source.
450
+ return ordered.length === 0 ? local : equality * 100 + connected * 10 + local;
451
+ }
452
+ function rawPredicateSources(predicate, entries, tables) {
453
+ const sources = new Set();
454
+ return rawExpressionSources(predicate.left, entries, tables, sources) &&
455
+ rawExpressionSources(predicate.right, entries, tables, sources)
456
+ ? sources
457
+ : undefined;
458
+ }
459
+ function rawExpressionSources(expression, entries, tables, into) {
460
+ if (expression.kind === "subquery" || expression.kind === "exists")
461
+ return false;
462
+ if (expression.kind === "column") {
463
+ const parts = expression.reference.split(".");
464
+ let matches;
465
+ if (parts.length === 2) {
466
+ const match = entries.findIndex(({ source }) => source.alias === parts[0]);
467
+ matches = match < 0 || tables[match]?.columns.has(parts[1] ?? "") !== true ? [] : [match];
468
+ }
469
+ else {
470
+ const column = parts[0] ?? "";
471
+ matches = tables.flatMap((table, index) => table?.columns.has(column) === true ? [index] : []);
472
+ }
473
+ if (matches.length !== 1)
474
+ return false;
475
+ into.add(matches[0] ?? -1);
476
+ return true;
477
+ }
478
+ return childExpressions(expression).every((child) => rawExpressionSources(child, entries, tables, into));
479
+ }
480
+ function predicateJoinsSource(predicate, candidate, available, entries, tables) {
481
+ if (predicate.operator !== "=")
482
+ return false;
483
+ const left = new Set();
484
+ const right = new Set();
485
+ if (!rawExpressionSources(predicate.left, entries, tables, left) ||
486
+ !rawExpressionSources(predicate.right, entries, tables, right)) {
487
+ return false;
488
+ }
489
+ const candidateOnly = (sources) => sources.size === 1 && sources.has(candidate);
490
+ const availableOnly = (sources) => sources.size > 0 && [...sources].every((source) => available.has(source));
491
+ return ((candidateOnly(left) && availableOnly(right)) || (candidateOnly(right) && availableOnly(left)));
492
+ }
318
493
  function bindPlan(plan, tables, memory, ftsStats) {
494
+ plan = orderCartesianJoins(plan, tables);
319
495
  const sources = [plan.base, ...plan.joins];
320
496
  const sourceTables = sources.map((source) => {
321
497
  const table = tables.get(source.table);
@@ -422,6 +598,7 @@ function bindPlan(plan, tables, memory, ftsStats) {
422
598
  ...(nulls === undefined ? {} : { nulls }),
423
599
  }));
424
600
  const grouped = groupBy.length > 0 || aggregateSpecs.length > 0;
601
+ const sourceOrdered = sourceTables[0]?.orderedForPlan === true && standardJoins.length === 0 && !grouped;
425
602
  // A single bare string-column GROUP BY can group on dictionary codes: identical values share a
426
603
  // code within one vector, so a code-indexed slot table replaces per-row hashing. Streamed
427
604
  // vectors swap dictionaries per window; the accumulator remaps its slot table by value on
@@ -440,9 +617,11 @@ function bindPlan(plan, tables, memory, ftsStats) {
440
617
  groupBy,
441
618
  groupIndexBySignature,
442
619
  aggregates: aggregateSpecs,
620
+ hasListAggregate: aggregateSpecs.some((aggregate) => aggregate.name === "JSON_ARRAYAGG" || aggregate.name === "STRING_AGG"),
443
621
  select,
444
622
  orderBy,
445
623
  grouped,
624
+ sourceOrdered,
446
625
  ...(codeGrouping === undefined ? {} : { codeGrouping }),
447
626
  wildcard: plan.select[0]?.expression.kind === "wildcard",
448
627
  ...(plan.limit === undefined ? {} : { limit: plan.limit }),
@@ -513,7 +692,11 @@ function bindExpression(expression, sources, aggregateSpecs, aggregateIndexes, m
513
692
  };
514
693
  }
515
694
  if (expression.kind === "literal" || expression.kind === "wildcard") {
516
- return { ...expression, signature };
695
+ return expression.kind === "literal" &&
696
+ typeof expression.value === "string" &&
697
+ expression.internalSqlValue !== true
698
+ ? { ...expression, value: protectedSqlTextValue(expression.value), signature }
699
+ : { ...expression, signature };
517
700
  }
518
701
  if (expression.kind === "column") {
519
702
  const parts = expression.reference.split(".");
@@ -599,12 +782,26 @@ function bindExpression(expression, sources, aggregateSpecs, aggregateIndexes, m
599
782
  argument.vector.kind === "datetime"
600
783
  ? { source: argument.source, vector: argument.vector }
601
784
  : undefined;
602
- const rawNumber = argument.kind === "column" && argument.vector.kind === "number"
785
+ const rawNumber = expression.name !== "JSON_ARRAYAGG" &&
786
+ expression.name !== "STRING_AGG" &&
787
+ argument.kind === "column" &&
788
+ argument.vector.kind === "number"
603
789
  ? { source: argument.source, vector: argument.vector }
604
790
  : undefined;
605
791
  aggregateSpecs.push({
606
792
  name: expression.name,
607
793
  argument,
794
+ ...(expression.name === "STRING_AGG"
795
+ ? { delimiter: required(arguments_[1], "STRING_AGG delimiter is missing") }
796
+ : {}),
797
+ ...(expression.aggregateOrderBy === undefined
798
+ ? {}
799
+ : {
800
+ orderBy: expression.aggregateOrderBy.map((order) => ({
801
+ ...order,
802
+ expression: bindExpression(order.expression, sources, aggregateSpecs, aggregateIndexes, memory, ftsBySignature, ftsStats),
803
+ })),
804
+ }),
608
805
  // The signature this slot was keyed by is the JSON of the compiled call, which carries the
609
806
  // flag — so COUNT(x) and COUNT(DISTINCT x) in one select land in separate slots.
610
807
  ...(expression.distinct === true ? { distinct: true } : {}),
@@ -674,7 +871,8 @@ function ensureFtsDictionaryCache(expression, columnIndex, vector, scoring) {
674
871
  const tokenCount = withScores ? new Uint32Array(vector.dictionary.length) : undefined;
675
872
  const termTf = withScores ? new Uint32Array(vector.dictionary.length * termCount) : undefined;
676
873
  for (let code = 0; code < vector.dictionary.length; code += 1) {
677
- const tokens = tokenize(vector.dictionary[code] ?? "");
874
+ const rendered = externalSqlDomainValue(vector.dictionary[code] ?? "");
875
+ const tokens = tokenize(typeof rendered === "string" ? rendered : "");
678
876
  masks[code] = termsMask(tokens, expression.terms);
679
877
  if (tokenCount !== undefined)
680
878
  tokenCount[code] = tokens.length;
@@ -922,8 +1120,10 @@ function runScanBatch(plan, start, length, groups, output, memory) {
922
1120
  const scan = sourceRows[plan.scanSource];
923
1121
  if (scan === undefined)
924
1122
  return false;
925
- for (let index = 0; index < length; index += 1)
926
- scan[index] = start + index;
1123
+ const order = plan.sourceTables[plan.scanSource]?.scanOrder;
1124
+ for (let index = 0; index < length; index += 1) {
1125
+ scan[index] = order?.[start + index] ?? start + index;
1126
+ }
927
1127
  const batch = { length, rowsBySource: sourceRows, memory: batchMemory };
928
1128
  return consumeJoinedBatches(plan, batch, 0, groups, output, memory);
929
1129
  }
@@ -938,7 +1138,7 @@ function executeBoundPlan(plan, memory) {
938
1138
  const groups = new GroupAccumulator(plan, memory);
939
1139
  const output = new ResultSink(plan, memory, true);
940
1140
  const scanRows = plan.sourceTables[plan.scanSource]?.rowCount ?? 0;
941
- const narrowed = ascendingScanRange(plan, 0, scanRows);
1141
+ const narrowed = plan.sourceOrdered ? undefined : ascendingScanRange(plan, 0, scanRows);
942
1142
  const ranges = narrowed?.ranges ?? [
943
1143
  { begin: narrowed?.begin ?? 0, end: narrowed?.end ?? scanRows },
944
1144
  ];
@@ -972,7 +1172,7 @@ async function executeBoundPlanAsync(plan, memory, options) {
972
1172
  // window cannot answer instead of stepping through them. `windowEnd` is where the loader
973
1173
  // stopped, which is the block boundary — exactly the span the ordering check covers.
974
1174
  const windowEnd = typeof residentEnd === "number" && residentEnd > start ? residentEnd : start + length;
975
- const narrowed = ascendingScanRange(plan, start, windowEnd);
1175
+ const narrowed = plan.sourceOrdered ? undefined : ascendingScanRange(plan, start, windowEnd);
976
1176
  if (narrowed === undefined) {
977
1177
  if (runScanBatch(plan, start, length, groups, output, memory))
978
1178
  break;
@@ -1161,8 +1361,18 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
1161
1361
  const raw = spec.argument.kind === "wildcard"
1162
1362
  ? 1
1163
1363
  : evaluateBatchExpression(plan, spec.argument, batch, row);
1164
- spillRow[`a${String(index)}`] =
1165
- raw === null || raw === undefined ? null : asQueryValue(raw);
1364
+ if (spec.name === "STRING_AGG" && raw !== null && raw !== undefined) {
1365
+ const delimiter = required(spec.delimiter, "STRING_AGG delimiter is missing");
1366
+ spillRow[`a${String(index)}`] = JSON.stringify([
1367
+ raw,
1368
+ evaluateBatchExpression(plan, delimiter, batch, row),
1369
+ (spec.orderBy ?? []).map((order) => encodeSpillValue(asQueryValue(evaluateBatchExpression(plan, order.expression, batch, row)))),
1370
+ ]);
1371
+ }
1372
+ else {
1373
+ spillRow[`a${String(index)}`] =
1374
+ raw === null || raw === undefined ? null : asQueryValue(raw);
1375
+ }
1166
1376
  }
1167
1377
  batchMemory.tally(queryRowPayloadBytes(spillRow), "Hash spill value row");
1168
1378
  const partition = hashQueryValues(groupValues) & (partitionCount - 1);
@@ -1446,7 +1656,7 @@ function createSpillRunReader(store, ownerId, run, columns, memory) {
1446
1656
  }
1447
1657
  function compareOrderedRows(left, right, orderBy) {
1448
1658
  for (const order of orderBy) {
1449
- const placed = explicitNullOrder(left[order.outputName], right[order.outputName], order.nulls);
1659
+ const placed = nullOrder(left[order.outputName], right[order.outputName], order.nulls, order.direction);
1450
1660
  if (placed !== undefined && placed !== 0)
1451
1661
  return placed;
1452
1662
  const comparison = compareValues(left[order.outputName], right[order.outputName]);
@@ -1533,7 +1743,7 @@ function executeMetadataCount(plan, memory) {
1533
1743
  }
1534
1744
  function finishResult(plan, inputRows, memory) {
1535
1745
  const rows = inputRows;
1536
- if (plan.orderBy.length > 0) {
1746
+ if (plan.orderBy.length > 0 && !plan.sourceOrdered) {
1537
1747
  const ordering = memory.reserve(safeMemoryProduct(rows.length, Uint32Array.BYTES_PER_ELEMENT * 2 +
1538
1748
  Uint8Array.BYTES_PER_ELEMENT +
1539
1749
  // Per extracted sort key: one slot, plus the null mask a numeric column adds beside
@@ -2110,7 +2320,7 @@ function consumeJoinedBatches(plan, batch, joinIndex, groups, output, memory, pr
2110
2320
  let working = batch;
2111
2321
  let complete = prefiltered;
2112
2322
  let owned;
2113
- if (joinIndex === 0 && !prefiltered) {
2323
+ if (!prefiltered) {
2114
2324
  const filtered = prefilterBatch(plan, batch, join);
2115
2325
  if (filtered !== undefined) {
2116
2326
  complete = filtered.complete;
@@ -2144,7 +2354,7 @@ function consumeJoinedBatches(plan, batch, joinIndex, groups, output, memory, pr
2144
2354
  function reachedEarlyLimit(plan, outputRows) {
2145
2355
  // Early termination must still produce the rows the trailing OFFSET will discard.
2146
2356
  return (!plan.grouped &&
2147
- plan.orderBy.length === 0 &&
2357
+ (plan.orderBy.length === 0 || plan.sourceOrdered) &&
2148
2358
  plan.limit !== undefined &&
2149
2359
  outputRows >= plan.limit + (plan.offset ?? 0));
2150
2360
  }
@@ -2186,11 +2396,18 @@ function* joinBatches(plan, input, join, memory) {
2186
2396
  const probeKey = evaluateBatchExpression(plan, join.probe, input, row);
2187
2397
  let buildRow = probeKey === null ? -1 : join.lookup.firstRow(probeKey);
2188
2398
  if (buildRow < 0) {
2189
- if (join.kind === "left") {
2399
+ if (join.kind === "left" || join.kind === "anti") {
2190
2400
  appendJoinedRow(ensureOutput(), outputLength, input, row, join.buildSource, -1);
2191
2401
  outputLength += 1;
2192
2402
  }
2193
2403
  }
2404
+ else if (join.kind === "semi") {
2405
+ appendJoinedRow(ensureOutput(), outputLength, input, row, join.buildSource, buildRow);
2406
+ outputLength += 1;
2407
+ }
2408
+ else if (join.kind === "anti") {
2409
+ // A match excludes the probe row.
2410
+ }
2194
2411
  else {
2195
2412
  while (buildRow >= 0) {
2196
2413
  appendJoinedRow(ensureOutput(), outputLength, input, row, join.buildSource, buildRow);
@@ -2254,12 +2471,16 @@ function* loopJoinBatches(plan, input, join, loop, memory) {
2254
2471
  if (!holds)
2255
2472
  continue;
2256
2473
  matched = true;
2257
- appendJoinedRow(ensureOutput(), outputLength, input, row, join.buildSource, buildRow);
2258
- outputLength += 1;
2259
- if (outputLength === DEFAULT_BATCH_ROWS)
2260
- yield* emit();
2474
+ if (join.kind !== "anti") {
2475
+ appendJoinedRow(ensureOutput(), outputLength, input, row, join.buildSource, buildRow);
2476
+ outputLength += 1;
2477
+ if (outputLength === DEFAULT_BATCH_ROWS)
2478
+ yield* emit();
2479
+ }
2480
+ if (join.kind === "semi" || join.kind === "anti")
2481
+ break;
2261
2482
  }
2262
- if (!matched && join.kind === "left") {
2483
+ if (!matched && (join.kind === "left" || join.kind === "anti")) {
2263
2484
  appendJoinedRow(ensureOutput(), outputLength, input, row, join.buildSource, -1);
2264
2485
  outputLength += 1;
2265
2486
  if (outputLength === DEFAULT_BATCH_ROWS)
@@ -2309,10 +2530,12 @@ function joinUniqueBatch(plan, input, join, memory) {
2309
2530
  cache[code] = buildRow;
2310
2531
  }
2311
2532
  }
2312
- if (buildRow < 0 && join.kind === "inner")
2533
+ if ((buildRow < 0 && (join.kind === "inner" || join.kind === "semi")) ||
2534
+ (buildRow >= 0 && join.kind === "anti")) {
2313
2535
  continue;
2536
+ }
2314
2537
  selectedRows[outputLength] = row;
2315
- buildRows[outputLength] = buildRow;
2538
+ buildRows[outputLength] = join.kind === "anti" ? -1 : buildRow;
2316
2539
  outputLength += 1;
2317
2540
  }
2318
2541
  }
@@ -2320,10 +2543,12 @@ function joinUniqueBatch(plan, input, join, memory) {
2320
2543
  for (let row = 0; row < input.length; row += 1) {
2321
2544
  const probeKey = evaluateBatchExpression(plan, join.probe, input, row);
2322
2545
  const buildRow = probeKey === null ? -1 : join.lookup.firstRow(probeKey);
2323
- if (buildRow < 0 && join.kind === "inner")
2546
+ if ((buildRow < 0 && (join.kind === "inner" || join.kind === "semi")) ||
2547
+ (buildRow >= 0 && join.kind === "anti")) {
2324
2548
  continue;
2549
+ }
2325
2550
  selectedRows[outputLength] = row;
2326
- buildRows[outputLength] = buildRow;
2551
+ buildRows[outputLength] = join.kind === "anti" ? -1 : buildRow;
2327
2552
  outputLength += 1;
2328
2553
  }
2329
2554
  }
@@ -2415,6 +2640,7 @@ class ResultSink {
2415
2640
  const capacity = (plan.limit ?? 0) + (plan.offset ?? 0);
2416
2641
  const scanRows = plan.sourceTables[plan.scanSource]?.rowCount ?? 0;
2417
2642
  const bounded = !plan.grouped &&
2643
+ !plan.sourceOrdered &&
2418
2644
  plan.orderBy.length > 0 &&
2419
2645
  plan.limit !== undefined &&
2420
2646
  (plan.joins.length > 0 || capacity < scanRows * BOUNDED_SINK_MAX_SHARE);
@@ -2692,7 +2918,7 @@ class ResultSink {
2692
2918
  const orderBy = this.#plan.orderBy;
2693
2919
  for (let index = 0; index < orderBy.length; index += 1) {
2694
2920
  const order = required(orderBy[index], "Order term is missing");
2695
- const placed = explicitNullOrder(left[index], right[index], order.nulls);
2921
+ const placed = nullOrder(left[index], right[index], order.nulls, order.direction);
2696
2922
  if (placed !== undefined && placed !== 0)
2697
2923
  return placed;
2698
2924
  const comparison = compareValues(left[index], right[index]);
@@ -2820,7 +3046,7 @@ class GroupAccumulator {
2820
3046
  * COUNT/SUM/AVG aggregates: one pass with unboxed reads and no per-row dispatch. Returns false
2821
3047
  * when the plan shape needs the generic per-row path.
2822
3048
  */
2823
- consumeFast(batch, passes, hasPredicates) {
3049
+ consumeFast(batch) {
2824
3050
  const plan = this.#plan;
2825
3051
  const codeGrouping = plan.codeGrouping;
2826
3052
  const globalGroup = plan.groupBy.length === 0;
@@ -2831,7 +3057,7 @@ class GroupAccumulator {
2831
3057
  if (specs === undefined)
2832
3058
  return false;
2833
3059
  // The purest shape — global COUNT(*) with no predicates — needs no row loop at all.
2834
- if (globalGroup && !hasPredicates && specs.every((spec) => spec.kind === "star")) {
3060
+ if (globalGroup && specs.every((spec) => spec.kind === "star")) {
2835
3061
  const state = required(this.#index.getEmpty(), "Grouped query state is missing");
2836
3062
  for (let index = 0; index < specs.length; index += 1) {
2837
3063
  state.counts[index] = (state.counts[index] ?? 0) + batch.length;
@@ -2881,8 +3107,6 @@ class GroupAccumulator {
2881
3107
  slots: groupVector.codes.length,
2882
3108
  };
2883
3109
  for (let row = 0; row < batch.length; row += 1) {
2884
- if (hasPredicates && !passes(row))
2885
- continue;
2886
3110
  let state = globalState;
2887
3111
  if (state === undefined && grouping !== undefined && states !== undefined) {
2888
3112
  const sourceRow = groupRows?.[row] ?? -1;
@@ -3101,7 +3325,7 @@ function consumeBatch(plan, batch, groups, output, memory, prefiltered = false)
3101
3325
  }
3102
3326
  return;
3103
3327
  }
3104
- if (plan.grouped && groups.consumeFast(batch, () => true, false))
3328
+ if (plan.grouped && groups.consumeFast(batch))
3105
3329
  return;
3106
3330
  if (!plan.grouped && output.tryAddBatch(batch))
3107
3331
  return;
@@ -3133,6 +3357,9 @@ function createGroupState(groupValues, plan, memory) {
3133
3357
  payloadBytes = safeMemorySum(payloadBytes, queryValuePayloadBytes(value), "Group state");
3134
3358
  }
3135
3359
  payloadBytes = safeMemorySum(payloadBytes, safeMemoryProduct(plan.aggregates.length, AGGREGATE_ACCUMULATOR_BYTES, "Aggregate accumulator state"), "Group state");
3360
+ if (plan.hasListAggregate) {
3361
+ payloadBytes = safeMemorySum(payloadBytes, safeMemoryProduct(plan.aggregates.length, QUERY_REFERENCE_BYTES, "JSON aggregate list slots"), "Group state");
3362
+ }
3136
3363
  // tally, not reserve: a group state lives until the context closes and is never released
3137
3364
  // on its own, so a per-group QueryMemoryReservation object — retained in the context's Set
3138
3365
  // for the whole query — is pure overhead at one per distinct group.
@@ -3162,6 +3389,9 @@ function createGroupState(groupValues, plan, memory) {
3162
3389
  counts: new Float64Array(plan.aggregates.length),
3163
3390
  sums: new Float64Array(plan.aggregates.length),
3164
3391
  values: new Array(plan.aggregates.length),
3392
+ ...(plan.hasListAggregate
3393
+ ? { lists: new Array(plan.aggregates.length) }
3394
+ : {}),
3165
3395
  valueReservations: new Array(plan.aggregates.length),
3166
3396
  valueReservationBytes: new Float64Array(plan.aggregates.length),
3167
3397
  distincts,
@@ -3236,7 +3466,11 @@ function updateAggregates(plan, state, batch, row, memory) {
3236
3466
  const value = spec.argument.kind === "wildcard"
3237
3467
  ? 1
3238
3468
  : evaluateBatchExpression(plan, spec.argument, batch, row);
3239
- applyAggregateValue(spec, state, index, value, memory);
3469
+ const delimiter = spec.delimiter === undefined
3470
+ ? undefined
3471
+ : evaluateBatchExpression(plan, spec.delimiter, batch, row);
3472
+ const orderValues = (spec.orderBy ?? []).map((order) => asQueryValue(evaluateBatchExpression(plan, order.expression, batch, row)));
3473
+ applyAggregateValue(spec, state, index, value, memory, delimiter, orderValues);
3240
3474
  }
3241
3475
  }
3242
3476
  /** Reads a float64 slot (number value or raw epoch milliseconds) unboxed, or null when invalid. */
@@ -3258,10 +3492,63 @@ function rawFloat64Value(vector, rowIndex) {
3258
3492
  function updateAggregatesFromValues(plan, state, values, memory) {
3259
3493
  for (let index = 0; index < plan.aggregates.length; index += 1) {
3260
3494
  const spec = required(plan.aggregates[index], "Aggregate specification is missing");
3261
- applyAggregateValue(spec, state, index, values[index], memory);
3495
+ const value = values[index];
3496
+ if (spec.name === "STRING_AGG" && typeof value === "string") {
3497
+ const decoded = JSON.parse(value);
3498
+ if (!Array.isArray(decoded) || decoded.length !== 3) {
3499
+ throw new Error("Spilled STRING_AGG input is invalid");
3500
+ }
3501
+ const encodedOrder = decoded[2];
3502
+ if (!Array.isArray(encodedOrder))
3503
+ throw new Error("Spilled STRING_AGG order is invalid");
3504
+ applyAggregateValue(spec, state, index, decoded[0], memory, decoded[1], encodedOrder.map(decodeSpillValue));
3505
+ }
3506
+ else {
3507
+ applyAggregateValue(spec, state, index, value, memory);
3508
+ }
3262
3509
  }
3263
3510
  }
3264
- function applyAggregateValue(spec, state, index, value, memory) {
3511
+ function applyAggregateValue(spec, state, index, value, memory, delimiter, orderValues = []) {
3512
+ if (spec.name === "JSON_ARRAYAGG") {
3513
+ const member = asQueryValue(value ?? null);
3514
+ if (spec.distinct === true && !firstOfItsKind(state, index, member, memory))
3515
+ return;
3516
+ state.counts[index] = (state.counts[index] ?? 0) + 1;
3517
+ const lists = required(state.lists, "JSON aggregate list state is missing");
3518
+ let list = lists[index];
3519
+ if (list === undefined) {
3520
+ list = [];
3521
+ lists[index] = list;
3522
+ }
3523
+ list.push(member);
3524
+ memory.tally(safeMemorySum(QUERY_REFERENCE_BYTES, queryValuePayloadBytes(member), "JSON_ARRAYAGG member"), "JSON_ARRAYAGG member");
3525
+ return;
3526
+ }
3527
+ if (spec.name === "STRING_AGG") {
3528
+ if (value === null || value === undefined)
3529
+ return;
3530
+ value = externalSqlDomainValue(value);
3531
+ delimiter = externalSqlDomainValue(delimiter);
3532
+ if (typeof value !== "string")
3533
+ throw new TypeError("STRING_AGG value must be a string");
3534
+ if (delimiter !== null && delimiter !== undefined && typeof delimiter !== "string") {
3535
+ throw new TypeError("STRING_AGG delimiter must be a string");
3536
+ }
3537
+ const distinctMember = JSON.stringify([value, delimiter ?? ""]);
3538
+ if (spec.distinct === true && !firstOfItsKind(state, index, distinctMember, memory))
3539
+ return;
3540
+ const member = JSON.stringify([value, delimiter ?? "", orderValues.map(encodeSpillValue)]);
3541
+ state.counts[index] = (state.counts[index] ?? 0) + 1;
3542
+ const lists = required(state.lists, "STRING_AGG list state is missing");
3543
+ let list = lists[index];
3544
+ if (list === undefined) {
3545
+ list = [];
3546
+ lists[index] = list;
3547
+ }
3548
+ list.push(member);
3549
+ memory.tally(safeMemorySum(QUERY_REFERENCE_BYTES, queryValuePayloadBytes(member), "STRING_AGG member"), "STRING_AGG member");
3550
+ return;
3551
+ }
3265
3552
  if (value === null || value === undefined)
3266
3553
  return;
3267
3554
  // One gate for every path that accumulates: the generic per-row path, the raw datetime path,
@@ -3270,7 +3557,16 @@ function applyAggregateValue(spec, state, index, value, memory) {
3270
3557
  return;
3271
3558
  state.counts[index] = (state.counts[index] ?? 0) + 1;
3272
3559
  if (spec.name === "SUM" || spec.name === "AVG") {
3273
- state.sums[index] = (state.sums[index] ?? 0) + numeric(value);
3560
+ if (isExactNumeric(value)) {
3561
+ const current = state.values[index];
3562
+ const sum = current === undefined ? value : exactNumericBinary("+", current, value);
3563
+ if (sum === null || sum === undefined)
3564
+ throw new Error("Exact NUMERIC sum disappeared");
3565
+ replaceAggregateValue(state, index, sum, `${spec.name} aggregate value`, memory);
3566
+ }
3567
+ else {
3568
+ state.sums[index] = (state.sums[index] ?? 0) + numeric(value);
3569
+ }
3274
3570
  }
3275
3571
  else if (spec.name === "MIN" &&
3276
3572
  (state.values[index] === undefined || compareValues(value, state.values[index]) < 0)) {
@@ -3364,12 +3660,55 @@ function evaluateFinalExpression(plan, expression, group) {
3364
3660
  return count;
3365
3661
  if (count === 0)
3366
3662
  return null;
3367
- const sum = group.sums[aggregateIndex] ?? 0;
3368
- if (expression.name === "SUM")
3369
- return sum;
3370
- if (expression.name === "AVG")
3371
- return sum / count;
3663
+ if (expression.name === "JSON_ARRAYAGG") {
3664
+ return JSON.stringify((required(group.lists, "JSON aggregate list state is missing")[aggregateIndex] ?? []).map(jsonValueOf));
3665
+ }
3666
+ if (expression.name === "STRING_AGG") {
3667
+ const members = required(group.lists, "STRING_AGG list state is missing")[aggregateIndex] ?? [];
3668
+ const decoded = members.map((encoded) => {
3669
+ if (typeof encoded !== "string")
3670
+ throw new Error("STRING_AGG member is invalid");
3671
+ const pair = JSON.parse(encoded);
3672
+ if (!Array.isArray(pair) ||
3673
+ typeof pair[0] !== "string" ||
3674
+ typeof pair[1] !== "string" ||
3675
+ !Array.isArray(pair[2])) {
3676
+ throw new Error("STRING_AGG member is invalid");
3677
+ }
3678
+ return {
3679
+ value: pair[0],
3680
+ delimiter: pair[1],
3681
+ order: pair[2].map(decodeSpillValue),
3682
+ };
3683
+ });
3684
+ const orderBy = plan.aggregates[aggregateIndex]?.orderBy ?? [];
3685
+ if (orderBy.length > 0) {
3686
+ decoded.sort((left, right) => {
3687
+ for (const [index, order] of orderBy.entries()) {
3688
+ const a = left.order[index];
3689
+ const b = right.order[index];
3690
+ const placed = nullOrder(a, b, order.nulls, order.direction);
3691
+ if (placed !== undefined && placed !== 0)
3692
+ return placed;
3693
+ const compared = compareValues(a, b);
3694
+ if (compared !== 0)
3695
+ return order.direction === "desc" ? -compared : compared;
3696
+ }
3697
+ return 0;
3698
+ });
3699
+ }
3700
+ return protectedSqlTextValue(decoded
3701
+ .map((member, index) => (index === 0 ? member.value : member.delimiter + member.value))
3702
+ .join(""));
3703
+ }
3372
3704
  const value = group.values[aggregateIndex] ?? null;
3705
+ if (expression.name === "SUM")
3706
+ return isExactNumeric(value) ? value : (group.sums[aggregateIndex] ?? 0);
3707
+ if (expression.name === "AVG") {
3708
+ if (isExactNumeric(value))
3709
+ return exactNumericBinary("/", value, count);
3710
+ return (group.sums[aggregateIndex] ?? 0) / count;
3711
+ }
3373
3712
  // Raw-millisecond datetime extremes re-box into a Date only here, once per surviving group.
3374
3713
  if (typeof value === "number" && plan.aggregates[aggregateIndex]?.rawDatetime !== undefined) {
3375
3714
  return new Date(value);
@@ -3598,19 +3937,24 @@ function detectDictionaryLike(predicate) {
3598
3937
  */
3599
3938
  const dictionaryLikeCache = new WeakMap();
3600
3939
  function dictionaryLikeMatches(dictionary, pattern, caseInsensitive, escape) {
3940
+ const externalPattern = externalSqlDomainValue(pattern);
3941
+ if (typeof externalPattern !== "string")
3942
+ throw new TypeError("LIKE requires string operands");
3601
3943
  let patterns = dictionaryLikeCache.get(dictionary);
3602
3944
  if (patterns === undefined) {
3603
3945
  patterns = new Map();
3604
3946
  dictionaryLikeCache.set(dictionary, patterns);
3605
3947
  }
3606
- const key = `${caseInsensitive ? "i" : "s"}${escape ?? ""} ${pattern}`;
3948
+ const key = `${caseInsensitive ? "i" : "s"}${escape ?? ""} ${externalPattern}`;
3607
3949
  let matches = patterns.get(key);
3608
3950
  if (matches === undefined) {
3609
3951
  matches = new Uint8Array(dictionary.length);
3610
3952
  for (let index = 0; index < dictionary.length; index += 1) {
3611
- matches[index] = likeMatches(pattern, dictionary[index] ?? "", caseInsensitive, escape)
3612
- ? 1
3613
- : 0;
3953
+ const value = externalSqlDomainValue(dictionary[index] ?? "");
3954
+ matches[index] =
3955
+ typeof value === "string" && likeMatches(externalPattern, value, caseInsensitive, escape)
3956
+ ? 1
3957
+ : 0;
3614
3958
  }
3615
3959
  if (patterns.size >= 32)
3616
3960
  patterns.clear();
@@ -3709,17 +4053,24 @@ function booleanTruth(expression, evaluateValue) {
3709
4053
  if (operator === "LIKE" ||
3710
4054
  operator === "NOT LIKE" ||
3711
4055
  operator === "ILIKE" ||
3712
- operator === "NOT ILIKE") {
3713
- const value = evaluateValue(expression.left);
3714
- const pattern = evaluateValue(expression.right);
4056
+ operator === "NOT ILIKE" ||
4057
+ operator === "SIMILAR TO" ||
4058
+ operator === "NOT SIMILAR TO") {
4059
+ const value = externalSqlDomainValue(evaluateValue(expression.left));
4060
+ const pattern = externalSqlDomainValue(evaluateValue(expression.right));
3715
4061
  if (value === null || value === undefined || pattern === null || pattern === undefined) {
3716
4062
  return null;
3717
4063
  }
3718
4064
  if (typeof value !== "string" || typeof pattern !== "string") {
3719
4065
  throw new TypeError("LIKE requires string operands");
3720
4066
  }
3721
- const matched = likeMatches(pattern, value, operator === "ILIKE" || operator === "NOT ILIKE", expression.escape);
3722
- return operator === "LIKE" || operator === "ILIKE" ? matched : !matched;
4067
+ const similar = operator === "SIMILAR TO" || operator === "NOT SIMILAR TO";
4068
+ const matched = similar
4069
+ ? compileSimilarPattern(pattern, expression.escape ?? "\\").test(value)
4070
+ : likeMatches(pattern, value, operator === "ILIKE" || operator === "NOT ILIKE", expression.escape);
4071
+ return operator === "LIKE" || operator === "ILIKE" || operator === "SIMILAR TO"
4072
+ ? matched
4073
+ : !matched;
3723
4074
  }
3724
4075
  if (operator === "IS DISTINCT FROM" || operator === "IS NOT DISTINCT FROM") {
3725
4076
  const distinct = distinctFromComparison(evaluateValue(expression.left), evaluateValue(expression.right));
@@ -3732,9 +4083,9 @@ function booleanTruth(expression, evaluateValue) {
3732
4083
  const a = comparable(left);
3733
4084
  const b = comparable(right);
3734
4085
  if (operator === "=")
3735
- return a === b;
4086
+ return compareValues(a, b) === 0;
3736
4087
  if (operator === "!=" || operator === "<>")
3737
- return a !== b;
4088
+ return compareValues(a, b) !== 0;
3738
4089
  const comparison = compareValues(a, b);
3739
4090
  if (operator === ">")
3740
4091
  return comparison > 0;
@@ -3933,8 +4284,11 @@ function binaryValue(operator, left, right) {
3933
4284
  if (typeof left !== "string" || typeof right !== "string") {
3934
4285
  throw new TypeError("|| requires string operands");
3935
4286
  }
3936
- return left + right;
4287
+ return protectedSqlTextValue(String(externalSqlDomainValue(left)) + String(externalSqlDomainValue(right)));
3937
4288
  }
4289
+ const exact = exactNumericBinary(operator, left, right);
4290
+ if (exact !== undefined)
4291
+ return exact;
3938
4292
  const a = numeric(left);
3939
4293
  const b = numeric(right);
3940
4294
  if (operator === "+")
@@ -3963,7 +4317,11 @@ function comparisonValue(operator, leftValue, rightValue) {
3963
4317
  if (operator === "LIKE" ||
3964
4318
  operator === "NOT LIKE" ||
3965
4319
  operator === "ILIKE" ||
3966
- operator === "NOT ILIKE") {
4320
+ operator === "NOT ILIKE" ||
4321
+ operator === "SIMILAR TO" ||
4322
+ operator === "NOT SIMILAR TO") {
4323
+ leftValue = externalSqlDomainValue(leftValue);
4324
+ rightValue = externalSqlDomainValue(rightValue);
3967
4325
  if (leftValue === null ||
3968
4326
  leftValue === undefined ||
3969
4327
  rightValue === null ||
@@ -3973,8 +4331,13 @@ function comparisonValue(operator, leftValue, rightValue) {
3973
4331
  if (typeof leftValue !== "string" || typeof rightValue !== "string") {
3974
4332
  throw new TypeError("LIKE requires string operands");
3975
4333
  }
3976
- const matched = likeMatches(rightValue, leftValue, operator === "ILIKE" || operator === "NOT ILIKE");
3977
- return operator === "LIKE" || operator === "ILIKE" ? matched : !matched;
4334
+ const similar = operator === "SIMILAR TO" || operator === "NOT SIMILAR TO";
4335
+ const matched = similar
4336
+ ? compileSimilarPattern(rightValue).test(leftValue)
4337
+ : likeMatches(rightValue, leftValue, operator === "ILIKE" || operator === "NOT ILIKE");
4338
+ return operator === "LIKE" || operator === "ILIKE" || operator === "SIMILAR TO"
4339
+ ? matched
4340
+ : !matched;
3978
4341
  }
3979
4342
  if (leftValue === null ||
3980
4343
  leftValue === undefined ||
@@ -3985,9 +4348,9 @@ function comparisonValue(operator, leftValue, rightValue) {
3985
4348
  const left = comparable(leftValue);
3986
4349
  const right = comparable(rightValue);
3987
4350
  if (operator === "=")
3988
- return left === right;
4351
+ return compareValues(left, right) === 0;
3989
4352
  if (operator === "!=" || operator === "<>")
3990
- return left !== right;
4353
+ return compareValues(left, right) !== 0;
3991
4354
  const comparison = compareValues(left, right);
3992
4355
  if (operator === ">")
3993
4356
  return comparison > 0;
@@ -4039,6 +4402,15 @@ function stableSortRows(rows, orderBy) {
4039
4402
  }
4040
4403
  }
4041
4404
  function compareValues(left, right) {
4405
+ const collated = collatedDomainCompare(left, right);
4406
+ if (collated !== undefined)
4407
+ return collated;
4408
+ const enumOrder = enumDomainCompare(left, right);
4409
+ if (enumOrder !== undefined)
4410
+ return enumOrder;
4411
+ const exact = exactNumericCompare(left, right);
4412
+ if (exact !== undefined)
4413
+ return exact;
4042
4414
  const a = left instanceof Date ? left.getTime() : left;
4043
4415
  const b = right instanceof Date ? right.getTime() : right;
4044
4416
  if (a === b)