@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,5 +1,5 @@
1
1
  import { dateIsoString, dateMilliseconds } from "../date-value.js";
2
- import { childExpressions, expressionAliases, forEachBlockExpression, forEachNestedBlock, isScalarFunctionName, scalarFunctionNames, scalarFunctionValue, splitCondition, volatileScalarFunctionNames, } from "./query.js";
2
+ import { childExpressions, expressionAliases, forEachBlockExpression, forEachNestedBlock, isScalarFunctionName, parseQuantified, scalarFunctionNames, scalarFunctionValue, splitCondition, volatileScalarFunctionNames, } from "./query.js";
3
3
  import { isSqlDomainValue } from "./sql-domains.js";
4
4
  /**
5
5
  * Deterministic plan-to-plan rewrites over the shared compiled representation. Every rule
@@ -60,7 +60,7 @@ function optimizeBlock(block) {
60
60
  pruneDerivedProjections(block);
61
61
  combineDerivedLimit(block);
62
62
  }
63
- /** Converts equality-correlated LATERAL derived tables into ordinary set-at-a-time joins. */
63
+ /** Converts correlated LATERAL derived tables into ordinary set-at-a-time joins. */
64
64
  function decorrelateLateralSources(block) {
65
65
  if (block.base.lateral === true) {
66
66
  throw new TypeError("LATERAL needs a source to its left");
@@ -85,14 +85,14 @@ function decorrelateLateralSources(block) {
85
85
  inner.limit !== undefined) {
86
86
  throw new TypeError("Correlated LATERAL queries cannot use grouping, HAVING, ORDER BY, or LIMIT");
87
87
  }
88
- const keys = extractCorrelation(inner, available, "LATERAL");
88
+ const keys = extractCorrelation(inner, available, "LATERAL", true);
89
89
  const keyAliases = keys.map((_, index) => `\u0000lateral_key_${String(index + 1)}`);
90
90
  keys.forEach((key, index) => {
91
91
  inner.select.push({ expression: key.inner, alias: keyAliases[index] ?? "" });
92
92
  });
93
93
  const correlations = keys.map((key, index) => ({
94
94
  kind: "condition",
95
- operator: key.operator,
95
+ operator: reverseComparison(key.operator),
96
96
  left: key.outer,
97
97
  right: { kind: "column", reference: `${join.alias}.${keyAliases[index] ?? ""}` },
98
98
  }));
@@ -372,6 +372,12 @@ function coalesceOrEqualityLists(block) {
372
372
  }
373
373
  const comparisonOperators = new Set(["=", "!=", "<>", ">", ">=", "<", "<="]);
374
374
  const aggregateCallNames = new Set(["COUNT", "SUM", "AVG", "MIN", "MAX"]);
375
+ const correlationKeyAlias = (index) => `\0correlation_key_${String(index)}`;
376
+ const correlationProbeAlias = (index) => `\0correlation_probe_${String(index)}`;
377
+ const correlationValueAlias = "\0correlation_value";
378
+ const correlationMarkerAlias = "\0correlation_match";
379
+ const correlationCountAlias = "\0correlation_count";
380
+ const correlationNonNullCountAlias = "\0correlation_non_null_count";
375
381
  function decorrelateBlock(block) {
376
382
  const scope = new Set([block.base.alias, ...block.joins.map((join) => join.alias)]);
377
383
  const nextAlias = correlationAliasFactory(scope);
@@ -382,15 +388,28 @@ function decorrelateBlock(block) {
382
388
  continue;
383
389
  rewritten.push(replacement ?? predicate);
384
390
  }
385
- block.predicates = rewritten;
391
+ block.predicates = rewritten.map((predicate) => ({
392
+ ...predicate,
393
+ left: rewriteCorrelatedExists(block, predicate.left, scope, nextAlias),
394
+ right: rewriteCorrelatedExists(block, predicate.right, scope, nextAlias),
395
+ }));
386
396
  const grouped = block.groupBy.length > 0 || block.select.some((item) => containsAggregateCall(item.expression));
387
397
  for (const item of block.select) {
388
398
  if (!expressionHasCorrelatedSubquery(item.expression))
389
399
  continue;
390
400
  if (grouped) {
391
- throw new TypeError("Correlated select-list subqueries are not supported with grouping or aggregates");
401
+ const groupedExpressions = new Set(block.groupBy.map((group) => JSON.stringify(group)));
402
+ const outerReferences = correlatedOuterReferences(item.expression);
403
+ if (containsAggregateCall(item.expression) ||
404
+ outerReferences.some((reference) => !groupedExpressions.has(JSON.stringify({ kind: "column", reference })))) {
405
+ throw new TypeError("A grouped correlated select-list subquery must reference only GROUP BY columns and cannot be nested inside an aggregate");
406
+ }
392
407
  }
393
408
  item.expression = rewriteCorrelatedScalars(block, item.expression, scope, nextAlias);
409
+ // The decorrelated value is functionally determined by the outer grouping keys. Carrying it
410
+ // as one additional key makes that dependency explicit to both grouped executors.
411
+ if (grouped)
412
+ block.groupBy.push(structuredClone(item.expression));
394
413
  }
395
414
  assertNoCorrelation(block);
396
415
  }
@@ -406,6 +425,15 @@ function expressionHasCorrelatedSubquery(expression) {
406
425
  return false;
407
426
  return childExpressions(expression).some(expressionHasCorrelatedSubquery);
408
427
  }
428
+ /** Qualified outer references used by correlated scalar subqueries inside one expression. */
429
+ function correlatedOuterReferences(expression) {
430
+ if (expression.kind === "subquery" && blockReferencesOutside(expression.block)) {
431
+ const references = [];
432
+ collectOutsideReferences(expression.block, new Set(), references);
433
+ return references;
434
+ }
435
+ return childExpressions(expression).flatMap(correlatedOuterReferences);
436
+ }
409
437
  /** Replaces correlated scalar subqueries inside one select expression with decorrelated joins. */
410
438
  function rewriteCorrelatedScalars(block, expression, scope, nextAlias) {
411
439
  if (expression.kind === "subquery" && blockReferencesOutside(expression.block)) {
@@ -438,6 +466,37 @@ function rewriteCorrelatedScalars(block, expression, scope, nextAlias) {
438
466
  }
439
467
  return expression;
440
468
  }
469
+ /** Replaces correlated EXISTS nodes nested inside boolean expressions with hidden match flags. */
470
+ function rewriteCorrelatedExists(block, expression, scope, nextAlias) {
471
+ if (expression.kind === "exists" && blockReferencesOutside(expression.block)) {
472
+ return decorrelateExistsExpression(block, expression, scope, nextAlias);
473
+ }
474
+ if (expression.kind === "binary" ||
475
+ expression.kind === "condition" ||
476
+ expression.kind === "logical") {
477
+ expression.left = rewriteCorrelatedExists(block, expression.left, scope, nextAlias);
478
+ expression.right = rewriteCorrelatedExists(block, expression.right, scope, nextAlias);
479
+ return expression;
480
+ }
481
+ if (expression.kind === "call") {
482
+ expression.arguments = expression.arguments.map((argument) => rewriteCorrelatedExists(block, argument, scope, nextAlias));
483
+ return expression;
484
+ }
485
+ if (expression.kind === "not") {
486
+ expression.operand = rewriteCorrelatedExists(block, expression.operand, scope, nextAlias);
487
+ return expression;
488
+ }
489
+ if (expression.kind === "case") {
490
+ for (const branch of expression.branches) {
491
+ branch.when = rewriteCorrelatedExists(block, branch.when, scope, nextAlias);
492
+ branch.then = rewriteCorrelatedExists(block, branch.then, scope, nextAlias);
493
+ }
494
+ if (expression.otherwise !== undefined) {
495
+ expression.otherwise = rewriteCorrelatedExists(block, expression.otherwise, scope, nextAlias);
496
+ }
497
+ }
498
+ return expression;
499
+ }
441
500
  function correlationAliasFactory(scope) {
442
501
  let sequence = 0;
443
502
  return () => {
@@ -466,14 +525,16 @@ function decorrelatePredicate(block, predicate, scope, nextAlias) {
466
525
  if (!blockReferencesOutside(inner))
467
526
  return undefined;
468
527
  rejectGroupedInner(inner, "EXISTS");
469
- guardWildcard(block);
470
528
  const keys = extractCorrelation(inner, scope, "EXISTS", true);
471
529
  const alias = nextAlias();
472
530
  const derived = {
473
531
  sql: "(correlated exists)",
474
532
  base: inner.base,
475
533
  joins: inner.joins,
476
- select: keys.map((key, index) => ({ expression: key.inner, alias: `k${String(index)}` })),
534
+ select: keys.map((key, index) => ({
535
+ expression: key.inner,
536
+ alias: correlationKeyAlias(index),
537
+ })),
477
538
  predicates: inner.predicates,
478
539
  // Grouping by the keys deduplicates, so the join multiplies no outer row. The parser's
479
540
  // injected LIMIT 1 and any ORDER BY are dropped: existence ignores both.
@@ -488,7 +549,7 @@ function decorrelatePredicate(block, predicate, scope, nextAlias) {
488
549
  if (!negated)
489
550
  return "consumed";
490
551
  return {
491
- left: { kind: "column", reference: `${alias}.k0` },
552
+ left: { kind: "column", reference: `${alias}.${correlationKeyAlias(0)}` },
492
553
  operator: "IS NULL",
493
554
  right: { kind: "literal", value: null },
494
555
  };
@@ -504,8 +565,11 @@ function decorrelatePredicate(block, predicate, scope, nextAlias) {
504
565
  if (inner.select.length !== 1 || item === undefined || item.expression.kind === "wildcard") {
505
566
  throw new TypeError("An IN subquery must select exactly one column");
506
567
  }
507
- guardWildcard(block);
508
- const keys = extractCorrelation(inner, scope, "IN");
568
+ const keys = extractCorrelation(inner, scope, "IN", true);
569
+ const general = keys.some((key) => key.operator !== "=");
570
+ if (predicate.operator === "NOT IN" && general) {
571
+ return decorrelateRangeNotIn(block, predicate.left, inner, item.expression, keys, nextAlias);
572
+ }
509
573
  if (predicate.operator === "NOT IN") {
510
574
  return decorrelateNotIn(block, predicate.left, inner, item.expression, keys, nextAlias);
511
575
  }
@@ -515,21 +579,113 @@ function decorrelatePredicate(block, predicate, scope, nextAlias) {
515
579
  base: inner.base,
516
580
  joins: inner.joins,
517
581
  select: [
518
- ...keys.map((key, index) => ({ expression: key.inner, alias: `k${String(index)}` })),
519
- { expression: item.expression, alias: "v" },
582
+ ...keys.map((key, index) => ({
583
+ expression: key.inner,
584
+ alias: correlationKeyAlias(index),
585
+ })),
586
+ { expression: item.expression, alias: correlationValueAlias },
520
587
  ],
521
588
  predicates: inner.predicates,
522
589
  groupBy: [...keys.map((key) => key.inner), item.expression],
523
590
  having: [],
524
591
  orderBy: [],
525
592
  };
593
+ if (general) {
594
+ const join = generalCorrelationJoin(alias, derived, [
595
+ ...keys.map((_, index) => ({
596
+ kind: "column",
597
+ reference: `${alias}.${correlationKeyAlias(index)}`,
598
+ })),
599
+ { kind: "column", reference: `${alias}.${correlationValueAlias}` },
600
+ ], [...keys.map((key) => key.outer), predicate.left], [...keys.map((key) => key.operator), "="]);
601
+ join.kind = "semi";
602
+ block.joins.push(join);
603
+ return "consumed";
604
+ }
526
605
  pushCorrelationJoin(block, alias, derived, keys, "inner");
527
606
  return {
528
607
  left: predicate.left,
529
608
  operator: "=",
530
- right: { kind: "column", reference: `${alias}.v` },
609
+ right: { kind: "column", reference: `${alias}.${correlationValueAlias}` },
531
610
  };
532
611
  }
612
+ // outer comparison ANY / ALL (correlated subquery). At top-level WHERE, ANY is existence of a
613
+ // true comparison; ALL is absence of a false or unknown comparison. Empty sets therefore keep
614
+ // their standard false/true answers without materializing a per-row value list.
615
+ const quantified = parseQuantified(predicate.operator);
616
+ if (quantified !== undefined && predicate.right.kind === "subquery") {
617
+ const inner = predicate.right.block;
618
+ if (!blockReferencesOutside(inner))
619
+ return undefined;
620
+ rejectGroupedInner(inner, "ANY/ALL");
621
+ const item = inner.select[0];
622
+ if (inner.select.length !== 1 || item === undefined || item.expression.kind === "wildcard") {
623
+ throw new TypeError("An ANY/ALL subquery must select exactly one column");
624
+ }
625
+ const keys = extractCorrelation(inner, scope, "ANY/ALL", true);
626
+ const alias = nextAlias();
627
+ const derived = {
628
+ sql: "(correlated quantified comparison)",
629
+ base: inner.base,
630
+ joins: inner.joins,
631
+ select: [
632
+ ...keys.map((key, index) => ({
633
+ expression: key.inner,
634
+ alias: correlationKeyAlias(index),
635
+ })),
636
+ { expression: item.expression, alias: correlationValueAlias },
637
+ ],
638
+ predicates: inner.predicates,
639
+ groupBy: [...keys.map((key) => key.inner), item.expression],
640
+ having: [],
641
+ orderBy: [],
642
+ };
643
+ const join = generalCorrelationJoin(alias, derived, keys.map((_, index) => ({
644
+ kind: "column",
645
+ reference: `${alias}.${correlationKeyAlias(index)}`,
646
+ })), keys.map((key) => key.outer), keys.map((key) => key.operator));
647
+ const value = {
648
+ kind: "column",
649
+ reference: `${alias}.${correlationValueAlias}`,
650
+ };
651
+ const comparison = {
652
+ kind: "condition",
653
+ operator: quantified.comparison,
654
+ left: structuredClone(predicate.left),
655
+ right: value,
656
+ };
657
+ let quantifiedCondition = comparison;
658
+ if (quantified.quantifier === "all") {
659
+ quantifiedCondition = {
660
+ kind: "logical",
661
+ operator: "or",
662
+ left: {
663
+ kind: "logical",
664
+ operator: "or",
665
+ left: {
666
+ kind: "condition",
667
+ operator: "IS NULL",
668
+ left: structuredClone(predicate.left),
669
+ right: { kind: "literal", value: null },
670
+ },
671
+ right: {
672
+ kind: "condition",
673
+ operator: "IS NULL",
674
+ left: value,
675
+ right: { kind: "literal", value: null },
676
+ },
677
+ },
678
+ right: { kind: "not", operand: comparison },
679
+ };
680
+ }
681
+ join.on =
682
+ join.on === undefined
683
+ ? quantifiedCondition
684
+ : { kind: "logical", operator: "and", left: join.on, right: quantifiedCondition };
685
+ join.kind = quantified.quantifier === "all" ? "anti" : "semi";
686
+ block.joins.push(join);
687
+ return "consumed";
688
+ }
533
689
  // comparison against a correlated scalar aggregate
534
690
  if (!comparisonOperators.has(predicate.operator))
535
691
  return undefined;
@@ -566,9 +722,9 @@ function decorrelateNotIn(block, probe, inner, item, keys, nextAlias) {
566
722
  select: [
567
723
  ...keys.map((key, index) => ({
568
724
  expression: structuredClone(key.inner),
569
- alias: `k${String(index)}`,
725
+ alias: correlationKeyAlias(index),
570
726
  })),
571
- { expression: structuredClone(item), alias: "v" },
727
+ { expression: structuredClone(item), alias: correlationValueAlias },
572
728
  ],
573
729
  predicates: structuredClone(inner.predicates),
574
730
  groupBy: [...keys.map((key) => structuredClone(key.inner)), structuredClone(item)],
@@ -589,9 +745,9 @@ function decorrelateNotIn(block, probe, inner, item, keys, nextAlias) {
589
745
  right: tuple([
590
746
  ...keys.map((_, index) => ({
591
747
  kind: "column",
592
- reference: `${valuesAlias}.k${String(index)}`,
748
+ reference: `${valuesAlias}.${correlationKeyAlias(index)}`,
593
749
  })),
594
- { kind: "column", reference: `${valuesAlias}.v` },
750
+ { kind: "column", reference: `${valuesAlias}.${correlationValueAlias}` },
595
751
  ]),
596
752
  });
597
753
  const flagsAlias = nextAlias();
@@ -602,15 +758,15 @@ function decorrelateNotIn(block, probe, inner, item, keys, nextAlias) {
602
758
  select: [
603
759
  ...keys.map((key, index) => ({
604
760
  expression: structuredClone(key.inner),
605
- alias: `k${String(index)}`,
761
+ alias: correlationKeyAlias(index),
606
762
  })),
607
763
  {
608
764
  expression: { kind: "call", name: "COUNT", arguments: [{ kind: "wildcard" }] },
609
- alias: "n",
765
+ alias: correlationCountAlias,
610
766
  },
611
767
  {
612
768
  expression: { kind: "call", name: "COUNT", arguments: [structuredClone(item)] },
613
- alias: "nn",
769
+ alias: correlationNonNullCountAlias,
614
770
  },
615
771
  ],
616
772
  predicates: structuredClone(inner.predicates),
@@ -619,8 +775,14 @@ function decorrelateNotIn(block, probe, inner, item, keys, nextAlias) {
619
775
  orderBy: [],
620
776
  };
621
777
  pushCorrelationJoin(block, flagsAlias, flags, keys, "left");
622
- const total = { kind: "column", reference: `${flagsAlias}.n` };
623
- const nonNull = { kind: "column", reference: `${flagsAlias}.nn` };
778
+ const total = {
779
+ kind: "column",
780
+ reference: `${flagsAlias}.${correlationCountAlias}`,
781
+ };
782
+ const nonNull = {
783
+ kind: "column",
784
+ reference: `${flagsAlias}.${correlationNonNullCountAlias}`,
785
+ };
624
786
  const safe = {
625
787
  kind: "logical",
626
788
  operator: "or",
@@ -648,6 +810,128 @@ function decorrelateNotIn(block, probe, inner, item, keys, nextAlias) {
648
810
  right: { kind: "literal", value: true },
649
811
  };
650
812
  }
813
+ /**
814
+ * Range-correlated NOT IN uses a general anti-join for exact matches, then joins back one flag row
815
+ * per distinct outer probe tuple. The counts preserve empty-set and NULL poisoning semantics.
816
+ */
817
+ function decorrelateRangeNotIn(block, probe, inner, item, keys, nextAlias) {
818
+ const valuesAlias = nextAlias();
819
+ const values = {
820
+ sql: "(range-correlated not in values)",
821
+ base: structuredClone(inner.base),
822
+ joins: structuredClone(inner.joins),
823
+ select: [
824
+ ...keys.map((key, index) => ({
825
+ expression: structuredClone(key.inner),
826
+ alias: correlationKeyAlias(index),
827
+ })),
828
+ { expression: structuredClone(item), alias: correlationValueAlias },
829
+ ],
830
+ predicates: structuredClone(inner.predicates),
831
+ groupBy: [...keys.map((key) => structuredClone(key.inner)), structuredClone(item)],
832
+ having: [],
833
+ orderBy: [],
834
+ };
835
+ const exactMatch = generalCorrelationJoin(valuesAlias, values, [
836
+ ...keys.map((_, index) => ({
837
+ kind: "column",
838
+ reference: `${valuesAlias}.${correlationKeyAlias(index)}`,
839
+ })),
840
+ { kind: "column", reference: `${valuesAlias}.${correlationValueAlias}` },
841
+ ], [...keys.map((key) => structuredClone(key.outer)), structuredClone(probe)], [...keys.map((key) => key.operator), "="]);
842
+ exactMatch.kind = "anti";
843
+ block.joins.push(exactMatch);
844
+ const probes = outerProbeBlock(block, keys);
845
+ const probesAlias = nextAlias();
846
+ const innerRowsAlias = nextAlias();
847
+ const innerRows = {
848
+ sql: "(range-correlated not in rows)",
849
+ base: inner.base,
850
+ joins: inner.joins,
851
+ select: [
852
+ ...keys.map((key, index) => ({
853
+ expression: key.inner,
854
+ alias: correlationKeyAlias(index),
855
+ })),
856
+ { expression: item, alias: correlationValueAlias },
857
+ ],
858
+ predicates: inner.predicates,
859
+ groupBy: [],
860
+ having: [],
861
+ orderBy: [],
862
+ };
863
+ const probeReferences = keys.map((_, index) => ({
864
+ kind: "column",
865
+ reference: `${probesAlias}.${correlationProbeAlias(index)}`,
866
+ }));
867
+ const flags = {
868
+ sql: "(range-correlated not in flags)",
869
+ base: { table: probesAlias, alias: probesAlias, derived: probes },
870
+ joins: [
871
+ generalCorrelationJoin(innerRowsAlias, innerRows, keys.map((_, index) => ({
872
+ kind: "column",
873
+ reference: `${innerRowsAlias}.${correlationKeyAlias(index)}`,
874
+ })), probeReferences, keys.map(({ operator }) => operator)),
875
+ ],
876
+ select: [
877
+ ...probeReferences.map((expression, index) => ({
878
+ expression,
879
+ alias: correlationKeyAlias(index),
880
+ })),
881
+ {
882
+ expression: { kind: "call", name: "COUNT", arguments: [{ kind: "wildcard" }] },
883
+ alias: correlationCountAlias,
884
+ },
885
+ {
886
+ expression: {
887
+ kind: "call",
888
+ name: "COUNT",
889
+ arguments: [{ kind: "column", reference: `${innerRowsAlias}.${correlationValueAlias}` }],
890
+ },
891
+ alias: correlationNonNullCountAlias,
892
+ },
893
+ ],
894
+ predicates: [],
895
+ groupBy: probeReferences,
896
+ having: [],
897
+ orderBy: [],
898
+ };
899
+ const flagsAlias = nextAlias();
900
+ pushCorrelationJoin(block, flagsAlias, flags, keys.map((key) => ({ ...key, operator: "=" })), "left");
901
+ const total = {
902
+ kind: "column",
903
+ reference: `${flagsAlias}.${correlationCountAlias}`,
904
+ };
905
+ const nonNull = {
906
+ kind: "column",
907
+ reference: `${flagsAlias}.${correlationNonNullCountAlias}`,
908
+ };
909
+ return {
910
+ left: {
911
+ kind: "logical",
912
+ operator: "or",
913
+ left: {
914
+ kind: "condition",
915
+ operator: "IS NULL",
916
+ left: total,
917
+ right: { kind: "literal", value: null },
918
+ },
919
+ right: {
920
+ kind: "logical",
921
+ operator: "and",
922
+ left: {
923
+ kind: "condition",
924
+ operator: "IS NOT NULL",
925
+ left: structuredClone(probe),
926
+ right: { kind: "literal", value: null },
927
+ },
928
+ right: { kind: "condition", operator: "=", left: total, right: nonNull },
929
+ },
930
+ },
931
+ operator: "IS TRUE",
932
+ right: { kind: "literal", value: true },
933
+ };
934
+ }
651
935
  /**
652
936
  * Rewrites one correlated scalar-aggregate subquery into a left join against the grouped
653
937
  * aggregate, returning the expression that replaces the subquery node (COUNT wraps in COALESCE
@@ -656,8 +940,9 @@ function decorrelateNotIn(block, probe, inner, item, keys, nextAlias) {
656
940
  function decorrelateScalarSubquery(block, subquery, scope, nextAlias) {
657
941
  const inner = subquery.block;
658
942
  const item = inner.select[0];
659
- const isAggregate = item?.expression.kind === "call" && aggregateCallNames.has(item.expression.name);
660
- if (inner.select.length !== 1 || item === undefined || !isAggregate) {
943
+ if (inner.select.length !== 1 ||
944
+ item?.expression.kind !== "call" ||
945
+ !aggregateCallNames.has(item.expression.name)) {
661
946
  throw new TypeError("A correlated scalar subquery must select exactly one aggregate expression");
662
947
  }
663
948
  if (inner.groupBy.length > 0 ||
@@ -666,16 +951,21 @@ function decorrelateScalarSubquery(block, subquery, scope, nextAlias) {
666
951
  inner.limit !== undefined) {
667
952
  throw new TypeError("A correlated scalar subquery cannot use GROUP BY, HAVING, ORDER BY, or LIMIT");
668
953
  }
669
- guardWildcard(block);
670
- const keys = extractCorrelation(inner, scope, "scalar");
954
+ const keys = extractCorrelation(inner, scope, "scalar", true);
955
+ if (keys.some((key) => key.operator !== "=")) {
956
+ return decorrelateNonEqualityScalar(block, inner, item.expression, keys, nextAlias);
957
+ }
671
958
  const alias = nextAlias();
672
959
  const derived = {
673
960
  sql: "(correlated scalar)",
674
961
  base: inner.base,
675
962
  joins: inner.joins,
676
963
  select: [
677
- ...keys.map((key, index) => ({ expression: key.inner, alias: `k${String(index)}` })),
678
- { expression: item.expression, alias: "v" },
964
+ ...keys.map((key, index) => ({
965
+ expression: key.inner,
966
+ alias: correlationKeyAlias(index),
967
+ })),
968
+ { expression: item.expression, alias: correlationValueAlias },
679
969
  ],
680
970
  predicates: inner.predicates,
681
971
  groupBy: keys.map((key) => key.inner),
@@ -683,18 +973,232 @@ function decorrelateScalarSubquery(block, subquery, scope, nextAlias) {
683
973
  orderBy: [],
684
974
  };
685
975
  pushCorrelationJoin(block, alias, derived, keys, "left");
686
- let value = { kind: "column", reference: `${alias}.v` };
687
- if (item.expression.kind === "call" && item.expression.name === "COUNT") {
976
+ let value = {
977
+ kind: "column",
978
+ reference: `${alias}.${correlationValueAlias}`,
979
+ };
980
+ if (item.expression.name === "COUNT") {
688
981
  // COUNT over an empty group is 0, but the unmatched left-join side is NULL.
689
982
  value = { kind: "call", name: "COALESCE", arguments: [value, { kind: "literal", value: 0 }] };
690
983
  }
691
984
  return value;
692
985
  }
986
+ /**
987
+ * Aggregates a range-correlated scalar subquery once per distinct outer probe tuple. The probe
988
+ * table prevents a general join from multiplying outer rows and lets the final join use equality
989
+ * keys even when the original correlation uses `<`, `>=`, or another comparison.
990
+ */
991
+ function decorrelateNonEqualityScalar(block, inner, aggregate, keys, nextAlias) {
992
+ if (aggregate.aggregateOrderBy !== undefined) {
993
+ throw new TypeError("A range-correlated scalar aggregate cannot use aggregate ORDER BY");
994
+ }
995
+ const argument = aggregate.arguments[0];
996
+ if (aggregate.arguments.length !== 1 || argument === undefined) {
997
+ throw new TypeError("A correlated scalar aggregate must have exactly one argument");
998
+ }
999
+ const probes = outerProbeBlock(block, keys);
1000
+ const probesAlias = nextAlias();
1001
+ const innerRowsAlias = nextAlias();
1002
+ const innerRows = {
1003
+ sql: "(range-correlated scalar rows)",
1004
+ base: inner.base,
1005
+ joins: inner.joins,
1006
+ select: [
1007
+ ...keys.map((key, index) => ({
1008
+ expression: key.inner,
1009
+ alias: correlationKeyAlias(index),
1010
+ })),
1011
+ {
1012
+ expression: argument.kind === "wildcard" ? { kind: "literal", value: 1 } : argument,
1013
+ alias: correlationValueAlias,
1014
+ },
1015
+ ],
1016
+ predicates: inner.predicates,
1017
+ groupBy: [],
1018
+ having: [],
1019
+ orderBy: [],
1020
+ };
1021
+ const joinedAggregate = {
1022
+ ...aggregate,
1023
+ arguments: [{ kind: "column", reference: `${innerRowsAlias}.${correlationValueAlias}` }],
1024
+ };
1025
+ const probeReferences = keys.map((_, index) => ({
1026
+ kind: "column",
1027
+ reference: `${probesAlias}.${correlationProbeAlias(index)}`,
1028
+ }));
1029
+ const aggregateBlock = {
1030
+ sql: "(range-correlated scalar aggregate)",
1031
+ base: { table: probesAlias, alias: probesAlias, derived: probes },
1032
+ joins: [
1033
+ generalCorrelationJoin(innerRowsAlias, innerRows, keys.map((_, index) => ({
1034
+ kind: "column",
1035
+ reference: `${innerRowsAlias}.${correlationKeyAlias(index)}`,
1036
+ })), probeReferences, keys.map(({ operator }) => operator)),
1037
+ ],
1038
+ select: [
1039
+ ...probeReferences.map((expression, index) => ({
1040
+ expression,
1041
+ alias: correlationKeyAlias(index),
1042
+ })),
1043
+ { expression: joinedAggregate, alias: correlationValueAlias },
1044
+ ],
1045
+ predicates: [],
1046
+ groupBy: probeReferences,
1047
+ having: [],
1048
+ orderBy: [],
1049
+ };
1050
+ const aggregateAlias = nextAlias();
1051
+ pushCorrelationJoin(block, aggregateAlias, aggregateBlock, keys.map((key) => ({ ...key, operator: "=" })), "left");
1052
+ let value = {
1053
+ kind: "column",
1054
+ reference: `${aggregateAlias}.${correlationValueAlias}`,
1055
+ };
1056
+ if (aggregate.name === "COUNT") {
1057
+ value = { kind: "call", name: "COALESCE", arguments: [value, { kind: "literal", value: 0 }] };
1058
+ }
1059
+ return value;
1060
+ }
1061
+ /** Builds the hidden boolean flag used when correlated EXISTS is nested below OR/NOT/CASE. */
1062
+ function decorrelateExistsExpression(block, exists, scope, nextAlias) {
1063
+ const inner = exists.block;
1064
+ rejectGroupedInner(inner, "EXISTS");
1065
+ const keys = extractCorrelation(inner, scope, "EXISTS", true);
1066
+ if (keys.every(({ operator }) => operator === "=")) {
1067
+ const flagsAlias = nextAlias();
1068
+ const flags = {
1069
+ sql: "(nested correlated exists flags)",
1070
+ base: inner.base,
1071
+ joins: inner.joins,
1072
+ select: [
1073
+ ...keys.map((key, index) => ({
1074
+ expression: key.inner,
1075
+ alias: correlationKeyAlias(index),
1076
+ })),
1077
+ { expression: { kind: "literal", value: 1 }, alias: correlationMarkerAlias },
1078
+ ],
1079
+ predicates: inner.predicates,
1080
+ // EXISTS only needs one flag per correlation tuple. This is the direct equality-key path
1081
+ // used by auth scopes such as `all_access OR EXISTS (...)` and cannot multiply outer rows.
1082
+ groupBy: keys.map((key) => key.inner),
1083
+ having: [],
1084
+ orderBy: [],
1085
+ };
1086
+ pushCorrelationJoin(block, flagsAlias, flags, keys, "left");
1087
+ return {
1088
+ kind: "condition",
1089
+ operator: exists.negated ? "IS NULL" : "IS NOT NULL",
1090
+ left: { kind: "column", reference: `${flagsAlias}.${correlationMarkerAlias}` },
1091
+ right: { kind: "literal", value: null },
1092
+ };
1093
+ }
1094
+ const probes = outerProbeBlock(block, keys);
1095
+ const probesAlias = nextAlias();
1096
+ const innerRowsAlias = nextAlias();
1097
+ const innerRows = {
1098
+ sql: "(nested correlated exists rows)",
1099
+ base: inner.base,
1100
+ joins: inner.joins,
1101
+ select: keys.map((key, index) => ({
1102
+ expression: key.inner,
1103
+ alias: correlationKeyAlias(index),
1104
+ })),
1105
+ predicates: inner.predicates,
1106
+ groupBy: keys.map((key) => key.inner),
1107
+ having: [],
1108
+ orderBy: [],
1109
+ };
1110
+ const probeReferences = keys.map((_, index) => ({
1111
+ kind: "column",
1112
+ reference: `${probesAlias}.${correlationProbeAlias(index)}`,
1113
+ }));
1114
+ const flags = {
1115
+ sql: "(nested correlated exists flags)",
1116
+ base: { table: probesAlias, alias: probesAlias, derived: probes },
1117
+ joins: [
1118
+ generalCorrelationJoin(innerRowsAlias, innerRows, keys.map((_, index) => ({
1119
+ kind: "column",
1120
+ reference: `${innerRowsAlias}.${correlationKeyAlias(index)}`,
1121
+ })), probeReferences, keys.map(({ operator }) => operator)),
1122
+ ],
1123
+ select: [
1124
+ ...probeReferences.map((expression, index) => ({
1125
+ expression,
1126
+ alias: correlationKeyAlias(index),
1127
+ })),
1128
+ { expression: { kind: "literal", value: 1 }, alias: correlationMarkerAlias },
1129
+ ],
1130
+ predicates: [],
1131
+ groupBy: probeReferences,
1132
+ having: [],
1133
+ orderBy: [],
1134
+ };
1135
+ const flagsAlias = nextAlias();
1136
+ pushCorrelationJoin(block, flagsAlias, flags, keys.map((key) => ({ ...key, operator: "=" })), "left");
1137
+ return {
1138
+ kind: "condition",
1139
+ operator: exists.negated ? "IS NULL" : "IS NOT NULL",
1140
+ left: { kind: "column", reference: `${flagsAlias}.${correlationMarkerAlias}` },
1141
+ right: { kind: "literal", value: null },
1142
+ };
1143
+ }
1144
+ /** Distinct outer key tuples, preserving the FROM joins needed by every referenced source. */
1145
+ function outerProbeBlock(block, keys) {
1146
+ const sources = [block.base, ...block.joins];
1147
+ const sourceIndexes = keys.map((key) => {
1148
+ if (key.outer.kind !== "column") {
1149
+ throw new TypeError("Correlation probes require qualified outer columns");
1150
+ }
1151
+ const alias = key.outer.reference.split(".")[0] ?? "";
1152
+ const index = sources.findIndex((source) => source.alias === alias);
1153
+ if (index < 0)
1154
+ throw new TypeError(`Cannot resolve correlated outer source: ${alias}`);
1155
+ return index;
1156
+ });
1157
+ const lastSource = Math.max(...sourceIndexes);
1158
+ const expressions = keys.map((key) => structuredClone(key.outer));
1159
+ return {
1160
+ sql: "(correlation probes)",
1161
+ base: structuredClone(block.base),
1162
+ joins: structuredClone(block.joins.slice(0, lastSource)),
1163
+ select: expressions.map((expression, index) => ({
1164
+ expression,
1165
+ alias: correlationProbeAlias(index),
1166
+ })),
1167
+ predicates: [],
1168
+ groupBy: expressions,
1169
+ having: [],
1170
+ orderBy: [],
1171
+ };
1172
+ }
1173
+ /** A general ON join carrying one comparison for each corresponding expression pair. */
1174
+ function generalCorrelationJoin(alias, derived, inner, outer, operators) {
1175
+ let on;
1176
+ inner.forEach((left, index) => {
1177
+ const right = outer[index];
1178
+ const operator = operators[index];
1179
+ if (right === undefined || operator === undefined)
1180
+ return;
1181
+ const comparison = { kind: "condition", operator, left, right };
1182
+ on =
1183
+ on === undefined
1184
+ ? comparison
1185
+ : { kind: "logical", operator: "and", left: on, right: comparison };
1186
+ });
1187
+ return {
1188
+ table: alias,
1189
+ alias,
1190
+ derived,
1191
+ kind: "inner",
1192
+ left: { kind: "literal", value: null },
1193
+ right: { kind: "literal", value: null },
1194
+ ...(on === undefined ? {} : { on }),
1195
+ };
1196
+ }
693
1197
  function pushCorrelationJoin(block, alias, derived, keys, kind) {
694
1198
  const source = { table: alias, alias, derived };
695
1199
  const keyReference = (index) => ({
696
1200
  kind: "column",
697
- reference: `${alias}.k${String(index)}`,
1201
+ reference: `${alias}.${correlationKeyAlias(index)}`,
698
1202
  });
699
1203
  const first = keys[0];
700
1204
  if (keys.length === 1 && first?.operator === "=") {
@@ -766,7 +1270,8 @@ function extractCorrelation(inner, outerScope, label, comparisons = false) {
766
1270
  const leftover = [];
767
1271
  collectOutsideReferences(inner, new Set(), leftover);
768
1272
  if (leftover.length > 0) {
769
- throw new TypeError(`Correlated ${label} subqueries support only equality conditions between one inner and one outer qualified column (unsupported reference: ${leftover[0] ?? ""})`);
1273
+ const supported = comparisons ? "comparison" : "equality";
1274
+ throw new TypeError(`Correlated ${label} subqueries support only ${supported} conditions between one inner and one outer qualified column (unsupported reference: ${leftover[0] ?? ""})`);
770
1275
  }
771
1276
  if (keys.length === 0) {
772
1277
  throw new TypeError(`Correlated ${label} subquery has no usable correlation condition`);
@@ -822,11 +1327,6 @@ function rejectGroupedInner(inner, label) {
822
1327
  throw new TypeError(`Correlated ${label} subqueries cannot use GROUP BY or HAVING`);
823
1328
  }
824
1329
  }
825
- function guardWildcard(block) {
826
- if (block.select.some((item) => item.expression.kind === "wildcard")) {
827
- throw new TypeError("Correlated subqueries cannot be combined with SELECT *");
828
- }
829
- }
830
1330
  function assertNoCorrelation(block) {
831
1331
  const visit = (expression) => {
832
1332
  if (expression.kind === "subquery" || expression.kind === "exists") {
@@ -906,10 +1406,15 @@ function foldExpression(expression) {
906
1406
  : targetWord === "json" ||
907
1407
  targetWord === "jsonb" ||
908
1408
  targetWord === "uuid" ||
1409
+ targetWord === "date" ||
909
1410
  targetWord === "time" ||
910
1411
  targetWord === "interval"
911
1412
  ? { kind: targetWord }
912
- : undefined;
1413
+ : expression.name === "JSON_QUERY" ||
1414
+ expression.name === "JSON_OBJECT" ||
1415
+ expression.name === "JSON_ARRAY"
1416
+ ? { kind: "json" }
1417
+ : undefined;
913
1418
  return {
914
1419
  kind: "literal",
915
1420
  value: folded,
@@ -1258,6 +1763,8 @@ function referencedOutputAliases(block, source, singleSource) {
1258
1763
  for (const join of block.joins) {
1259
1764
  visit(join.left);
1260
1765
  visit(join.right);
1766
+ if (join.on !== undefined)
1767
+ visit(join.on);
1261
1768
  }
1262
1769
  for (const order of block.orderBy) {
1263
1770
  // An ORDER BY reference may name an output alias of the outer block rather than a source