@minnowdb/core 0.7.1 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/database.d.ts +6 -0
- package/dist/engine/database.js +391 -93
- package/dist/engine/optimizer.js +168 -87
- package/dist/engine/query.d.ts +98 -0
- package/dist/engine/query.js +1457 -162
- package/dist/engine/sql-domains.d.ts +11 -0
- package/dist/engine/sql-domains.js +56 -0
- package/dist/engine/sql-functions.js +129 -3
- package/dist/engine/sql-json.d.ts +2 -0
- package/dist/engine/sql-json.js +4 -0
- package/dist/engine/sql-semantics.d.ts +9 -1
- package/dist/engine/sql-semantics.js +19 -3
- package/dist/engine/vector.js +29 -13
- package/dist/plan/model.d.ts +14 -2
- package/dist/storage/indexeddb.js +9 -1
- package/dist/storage/memory.d.ts +2 -1
- package/dist/storage/memory.js +29 -4
- package/dist/storage/toolkit/record-core.js +15 -3
- package/dist/testing/sqllogictest.js +3 -1
- package/package.json +1 -1
- package/postgres-feature-profile.json +9 -4
- package/sql-feature-matrix.json +543 -6
package/dist/engine/optimizer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dateIsoString, dateMilliseconds } from "../date-value.js";
|
|
2
2
|
import { crossJoinPlan } from "../plan/model.js";
|
|
3
|
-
import { blockHasSubqueries, childExpressions, DUAL_TABLE, expressionAliases, forEachBlockExpression, forEachNestedBlock, hasAggregate, isAggregateCall, isScalarFunctionName, mapBlockExpressions, mapChildExpressions, parseQuantified, scalarFunctionNames, scalarFunctionValue, splitCondition, statementDatetimeNames, volatileScalarFunctionNames, transparentProjectionSource, dateTruncValue } from "./query.js";
|
|
3
|
+
import { blockHasRowWindow, blockHasSubqueries, childExpressions, DUAL_TABLE, expressionAliases, forEachBlockExpression, forEachNestedBlock, hasAggregate, isAggregateCall, isScalarFunctionName, mapBlockExpressions, mapChildExpressions, parseQuantified, scalarFunctionNames, scalarFunctionValue, splitCondition, statementDatetimeNames, volatileScalarFunctionNames, transparentProjectionSource, dateTruncValue, integerQuotient } from "./query.js";
|
|
4
4
|
import { concatenatedSqlValue, isSqlDomainValue } from "./sql-domains.js";
|
|
5
5
|
import { simpleScalarFunctions } from "./sql-functions.js";
|
|
6
6
|
function optimizePlan(plan, options = {}) {
|
|
@@ -199,7 +199,7 @@ function decorrelateLateralSources(block) {
|
|
|
199
199
|
const keyAliases = keys.map((_, index) => `\0lateral_key_${String(index + 1)}`);
|
|
200
200
|
const grouped = inner.groupBy.length > 0;
|
|
201
201
|
const globalAggregate = !grouped && inner.select.some((item) => containsAggregateCall(item.expression)) && inner.having.length === 0;
|
|
202
|
-
const ranked = inner
|
|
202
|
+
const ranked = blockHasRowWindow(inner);
|
|
203
203
|
const cross = join.on?.kind === "condition" && join.on.operator === "=" && join.on.left.kind === "literal" && join.on.left.value === 1 && join.on.right.kind === "literal" && join.on.right.value === 1 || join.on?.kind === "literal" && join.on.value === true;
|
|
204
204
|
if (grouped || globalAggregate || ranked || inner.having.length > 0) {
|
|
205
205
|
if (!keys.every((key) => key.operator === "=")) {
|
|
@@ -1409,7 +1409,7 @@ function decorrelateScalarSubquery(block, subquery, scope, nextAlias) {
|
|
|
1409
1409
|
if (passthrough !== void 0)
|
|
1410
1410
|
return passthrough;
|
|
1411
1411
|
const item = inner.select[0];
|
|
1412
|
-
if (inner.select.length !== 1 || item === void 0 || !isAggregateCall(item.expression) || inner.groupBy.length > 0 || inner.having.length > 0 || inner.orderBy.length > 0 || inner
|
|
1412
|
+
if (inner.select.length !== 1 || item === void 0 || !isAggregateCall(item.expression) || inner.groupBy.length > 0 || inner.having.length > 0 || inner.orderBy.length > 0 || blockHasRowWindow(inner) || !canExtractCorrelation(inner, scope, "scalar", true)) {
|
|
1413
1413
|
return decorrelateGeneralScalar(block, inner, scope, nextAlias);
|
|
1414
1414
|
}
|
|
1415
1415
|
const previewKeys = extractCorrelation(structuredClone(inner), scope, "scalar", true);
|
|
@@ -1432,7 +1432,10 @@ function decorrelateScalarSubquery(block, subquery, scope, nextAlias) {
|
|
|
1432
1432
|
})),
|
|
1433
1433
|
{ expression: item.expression, alias: correlationValueAlias }
|
|
1434
1434
|
],
|
|
1435
|
-
predicates:
|
|
1435
|
+
predicates: [
|
|
1436
|
+
...inner.predicates,
|
|
1437
|
+
...mirrorPredicatesThroughKeys(outerProbePredicates(block, keys.map((key) => key.outer), nextAlias.resolvedNames), keys)
|
|
1438
|
+
],
|
|
1436
1439
|
groupBy: keys.map((key) => key.inner),
|
|
1437
1440
|
having: [],
|
|
1438
1441
|
orderBy: []
|
|
@@ -1449,7 +1452,7 @@ function decorrelateScalarSubquery(block, subquery, scope, nextAlias) {
|
|
|
1449
1452
|
}
|
|
1450
1453
|
function scalarPassthrough(inner) {
|
|
1451
1454
|
const item = inner.select[0];
|
|
1452
|
-
if (inner.base.table !== DUAL_TABLE || inner.base.derived !== void 0 || inner.joins.length > 0 || inner.select.length !== 1 || item === void 0 || inner.predicates.length > 0 || inner.groupBy.length > 0 || inner.having.length > 0 || inner.orderBy.length > 0 || inner.offset !== void 0 || inner.limit !== void 0 && inner.limit < 1) {
|
|
1455
|
+
if (inner.base.table !== DUAL_TABLE || inner.base.derived !== void 0 || inner.joins.length > 0 || inner.select.length !== 1 || item === void 0 || inner.predicates.length > 0 || inner.groupBy.length > 0 || inner.having.length > 0 || inner.orderBy.length > 0 || inner.offset !== void 0 || inner.offsetParameter !== void 0 || inner.limitParameter !== void 0 || inner.limit !== void 0 && inner.limit < 1) {
|
|
1453
1456
|
return void 0;
|
|
1454
1457
|
}
|
|
1455
1458
|
return structuredClone(item.expression);
|
|
@@ -1463,7 +1466,7 @@ function scalarShape(inner) {
|
|
|
1463
1466
|
let expression = structuredClone(item.expression);
|
|
1464
1467
|
for (; ; ) {
|
|
1465
1468
|
const derived = rows.base.derived;
|
|
1466
|
-
if (derived === void 0 || rows.joins.length > 0 || rows.predicates.length > 0 || rows.groupBy.length > 0 || rows.having.length > 0 || rows.orderBy.length > 0 || rows
|
|
1469
|
+
if (derived === void 0 || rows.joins.length > 0 || rows.predicates.length > 0 || rows.groupBy.length > 0 || rows.having.length > 0 || rows.orderBy.length > 0 || blockHasRowWindow(rows) || rows.distinctWildcard === true) {
|
|
1467
1470
|
break;
|
|
1468
1471
|
}
|
|
1469
1472
|
expression = inlineDerivedProjection(expression, rows.base.alias, derived);
|
|
@@ -1494,14 +1497,17 @@ function scalarOrderExpression(expression, rows) {
|
|
|
1494
1497
|
const matches = rows.select.filter((item) => item.alias === expression.reference);
|
|
1495
1498
|
return matches.length === 1 ? structuredClone(matches[0]?.expression ?? expression) : structuredClone(expression);
|
|
1496
1499
|
}
|
|
1497
|
-
function emptyScalarExpression(expression) {
|
|
1500
|
+
function emptyScalarExpression(expression, preservedColumns = /* @__PURE__ */ new Set()) {
|
|
1498
1501
|
if (isAggregateCall(expression)) {
|
|
1499
1502
|
return { kind: "literal", value: expression.name === "COUNT" ? 0 : null };
|
|
1500
1503
|
}
|
|
1501
|
-
if (expression.kind === "column"
|
|
1504
|
+
if (expression.kind === "column") {
|
|
1505
|
+
return preservedColumns.has(expression.reference) ? structuredClone(expression) : { kind: "literal", value: null };
|
|
1506
|
+
}
|
|
1507
|
+
if (expression.kind === "wildcard") {
|
|
1502
1508
|
return { kind: "literal", value: null };
|
|
1503
1509
|
}
|
|
1504
|
-
return mapChildExpressions(expression, emptyScalarExpression);
|
|
1510
|
+
return mapChildExpressions(expression, (child) => emptyScalarExpression(child, preservedColumns));
|
|
1505
1511
|
}
|
|
1506
1512
|
function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
|
|
1507
1513
|
const shape = scalarShape(inner);
|
|
@@ -1512,14 +1518,44 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
|
|
|
1512
1518
|
if (rows.limitWithTies === true && rows.orderBy.length === 0) {
|
|
1513
1519
|
throw new TypeError("A correlated scalar WITH TIES query requires ORDER BY");
|
|
1514
1520
|
}
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1521
|
+
const expressionOutside = [];
|
|
1522
|
+
collectExpressionOutsideReferences(shape.expression, /* @__PURE__ */ new Set([rows.base.alias, ...rows.joins.map((join) => join.alias)]), expressionOutside);
|
|
1523
|
+
const probeLifted = expressionOutside.length > 0 || !canExtractCorrelation(rows, scope, "scalar", true);
|
|
1524
|
+
const references = probeLifted ? probeOuterReferences(rows, expressionOutside, scope, "scalar") : [];
|
|
1525
|
+
const keys = probeLifted ? takeCorrelationPredicates(rows, scope, true) : extractCorrelation(rows, scope, "scalar", true);
|
|
1526
|
+
const outerExpressions = probeLifted ? references.map((reference) => ({ kind: "column", reference })) : keys.map((key) => structuredClone(key.outer));
|
|
1527
|
+
const originalExpression = structuredClone(shape.expression);
|
|
1517
1528
|
const probes = outerProbeBlockForExpressions(block, outerExpressions, nextAlias.resolvedNames);
|
|
1518
1529
|
const probesAlias = nextAlias();
|
|
1519
1530
|
const probeReferences = outerExpressions.map((_, index) => ({
|
|
1520
1531
|
kind: "column",
|
|
1521
1532
|
reference: `${probesAlias}.${correlationProbeAlias(index)}`
|
|
1522
1533
|
}));
|
|
1534
|
+
if (probeLifted) {
|
|
1535
|
+
const replacements = new Map(references.map((reference, index) => [reference, probeReferences[index] ?? null]));
|
|
1536
|
+
replacePlanReferences(rows, replacements);
|
|
1537
|
+
shape.expression = replaceExpressionReferences(shape.expression, replacements);
|
|
1538
|
+
const source = { table: probesAlias, alias: probesAlias, derived: probes };
|
|
1539
|
+
const missingProbe = { kind: "literal", value: null };
|
|
1540
|
+
const keyProbes = keys.map((key) => {
|
|
1541
|
+
const reference = key.outer.kind === "column" ? key.outer.reference : "";
|
|
1542
|
+
return probeReferences[references.indexOf(reference)] ?? missingProbe;
|
|
1543
|
+
});
|
|
1544
|
+
const keysReadBase = keys.every((key) => {
|
|
1545
|
+
const parts = key.inner.kind === "column" ? key.inner.reference.split(".") : [];
|
|
1546
|
+
return parts.length === 1 || parts[0] === rows.base.alias;
|
|
1547
|
+
});
|
|
1548
|
+
if (keys.length > 0 && keysReadBase) {
|
|
1549
|
+
rows.joins.unshift(generalCorrelationJoin(probesAlias, probes, keys.map((key) => key.inner), keyProbes, keys.map((key) => key.operator)));
|
|
1550
|
+
} else {
|
|
1551
|
+
rows.joins.unshift(crossJoinPlan(source));
|
|
1552
|
+
rows.predicates.push(...keys.map((key, index) => ({
|
|
1553
|
+
left: key.inner,
|
|
1554
|
+
operator: key.operator,
|
|
1555
|
+
right: keyProbes[index] ?? missingProbe
|
|
1556
|
+
})));
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1523
1559
|
const finalRowsAlias = nextAlias();
|
|
1524
1560
|
const projected = [];
|
|
1525
1561
|
const project = (expression, alias) => {
|
|
@@ -1556,6 +1592,15 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
|
|
|
1556
1592
|
return rewritten;
|
|
1557
1593
|
}
|
|
1558
1594
|
if (expression.kind === "column" || expression.kind === "wildcard") {
|
|
1595
|
+
if (probeLifted && expression.kind === "column") {
|
|
1596
|
+
const index = probeReferences.findIndex((reference) => reference.kind === "column" && reference.reference === expression.reference);
|
|
1597
|
+
if (index >= 0) {
|
|
1598
|
+
return {
|
|
1599
|
+
kind: "column",
|
|
1600
|
+
reference: `${finalRowsAlias}.${correlationKeyAlias(index)}`
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1559
1604
|
throw new TypeError("A correlated scalar aggregate cannot select an ungrouped inner column");
|
|
1560
1605
|
}
|
|
1561
1606
|
if (expression.kind === "subquery" || expression.kind === "exists") {
|
|
@@ -1575,18 +1620,21 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
|
|
|
1575
1620
|
base: rows.base,
|
|
1576
1621
|
joins: rows.joins,
|
|
1577
1622
|
select: [
|
|
1578
|
-
...keys.map((key, index) => ({
|
|
1579
|
-
expression
|
|
1623
|
+
...(probeLifted ? probeReferences : keys.map((key) => key.inner)).map((expression, index) => ({
|
|
1624
|
+
expression,
|
|
1580
1625
|
alias: correlationKeyAlias(index)
|
|
1581
1626
|
})),
|
|
1582
1627
|
...projected
|
|
1583
1628
|
],
|
|
1584
|
-
predicates: [
|
|
1629
|
+
predicates: [
|
|
1630
|
+
...rows.predicates,
|
|
1631
|
+
...probeLifted ? [] : mirrorPredicatesThroughKeys(probes.predicates, keys)
|
|
1632
|
+
],
|
|
1585
1633
|
groupBy: [],
|
|
1586
1634
|
having: [],
|
|
1587
1635
|
orderBy: []
|
|
1588
1636
|
};
|
|
1589
|
-
const matched = {
|
|
1637
|
+
const matched = probeLifted ? rawRows : {
|
|
1590
1638
|
sql: "(correlated scalar matched probes)",
|
|
1591
1639
|
base: { table: probesAlias, alias: probesAlias, derived: probes },
|
|
1592
1640
|
joins: [
|
|
@@ -1611,7 +1659,7 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
|
|
|
1611
1659
|
orderBy: []
|
|
1612
1660
|
};
|
|
1613
1661
|
let finalRows = matched;
|
|
1614
|
-
if (rows
|
|
1662
|
+
if (blockHasRowWindow(rows)) {
|
|
1615
1663
|
const rankedAlias = nextAlias();
|
|
1616
1664
|
const rankedTable = nextAlias();
|
|
1617
1665
|
const ranked = {
|
|
@@ -1623,7 +1671,7 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
|
|
|
1623
1671
|
{
|
|
1624
1672
|
alias: correlationRowNumberAlias,
|
|
1625
1673
|
name: rows.limitWithTies === true ? "RANK" : "ROW_NUMBER",
|
|
1626
|
-
partitionAliases:
|
|
1674
|
+
partitionAliases: outerExpressions.map((_, index) => correlationKeyAlias(index)),
|
|
1627
1675
|
orderAliases: rows.orderBy.map((order, index) => ({
|
|
1628
1676
|
alias: correlationOrderAlias(index),
|
|
1629
1677
|
direction: order.direction,
|
|
@@ -1659,7 +1707,7 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
|
|
|
1659
1707
|
base: ranked,
|
|
1660
1708
|
joins: [],
|
|
1661
1709
|
select: [
|
|
1662
|
-
...
|
|
1710
|
+
...outerExpressions.map((_, index) => ({
|
|
1663
1711
|
expression: {
|
|
1664
1712
|
kind: "column",
|
|
1665
1713
|
reference: `${rankedAlias}.${correlationKeyAlias(index)}`
|
|
@@ -1679,7 +1727,7 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
|
|
|
1679
1727
|
...rows.offsetParameter === void 0 ? {} : { offsetValidationParameters: [rows.offsetParameter] }
|
|
1680
1728
|
};
|
|
1681
1729
|
}
|
|
1682
|
-
const finalProbeReferences =
|
|
1730
|
+
const finalProbeReferences = outerExpressions.map((_, index) => ({
|
|
1683
1731
|
kind: "column",
|
|
1684
1732
|
reference: `${finalRowsAlias}.${correlationKeyAlias(index)}`
|
|
1685
1733
|
}));
|
|
@@ -1716,7 +1764,7 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
|
|
|
1716
1764
|
left: marker,
|
|
1717
1765
|
right: { kind: "literal", value: null }
|
|
1718
1766
|
},
|
|
1719
|
-
then: grouped ? emptyScalarExpression(
|
|
1767
|
+
then: grouped ? emptyScalarExpression(originalExpression, new Set(references)) : { kind: "literal", value: null }
|
|
1720
1768
|
}
|
|
1721
1769
|
],
|
|
1722
1770
|
otherwise: {
|
|
@@ -1880,19 +1928,7 @@ function decorrelateExistsExpression(block, exists, scope, nextAlias) {
|
|
|
1880
1928
|
}
|
|
1881
1929
|
function decorrelateExistsWithProbes(block, exists, scope, nextAlias) {
|
|
1882
1930
|
const inner = exists.block;
|
|
1883
|
-
const
|
|
1884
|
-
collectOutsideReferences(inner, /* @__PURE__ */ new Set(), outside);
|
|
1885
|
-
const references = [...new Set(outside)];
|
|
1886
|
-
if (references.length === 0) {
|
|
1887
|
-
throw new TypeError("Correlated EXISTS subquery has no usable outer reference");
|
|
1888
|
-
}
|
|
1889
|
-
for (const reference of references) {
|
|
1890
|
-
const parts = reference.split(".");
|
|
1891
|
-
const alias = parts.length === 2 ? parts[0] : void 0;
|
|
1892
|
-
if (alias === void 0 || !scope.has(alias)) {
|
|
1893
|
-
throw new TypeError(`Correlated EXISTS references an unavailable outer source: ${reference}`);
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1931
|
+
const references = probeOuterReferences(inner, [], scope, "EXISTS");
|
|
1896
1932
|
const outerExpressions = references.map((reference) => ({
|
|
1897
1933
|
kind: "column",
|
|
1898
1934
|
reference
|
|
@@ -1959,33 +1995,64 @@ function decorrelateExistsWithProbes(block, exists, scope, nextAlias) {
|
|
|
1959
1995
|
right: { kind: "literal", value: null }
|
|
1960
1996
|
};
|
|
1961
1997
|
}
|
|
1962
|
-
function
|
|
1963
|
-
const rewrite = (
|
|
1964
|
-
if (
|
|
1965
|
-
const replacement = replacements.get(
|
|
1966
|
-
return replacement == null ?
|
|
1998
|
+
function replaceExpressionReferences(expression, replacements) {
|
|
1999
|
+
const rewrite = (expression2) => {
|
|
2000
|
+
if (expression2.kind === "column") {
|
|
2001
|
+
const replacement = replacements.get(expression2.reference);
|
|
2002
|
+
return replacement == null ? expression2 : structuredClone(replacement);
|
|
1967
2003
|
}
|
|
1968
|
-
if (
|
|
1969
|
-
replacePlanReferences(
|
|
1970
|
-
return
|
|
2004
|
+
if (expression2.kind === "subquery" || expression2.kind === "exists") {
|
|
2005
|
+
replacePlanReferences(expression2.block, replacements);
|
|
2006
|
+
return expression2;
|
|
1971
2007
|
}
|
|
1972
|
-
if (
|
|
1973
|
-
|
|
1974
|
-
for (const order of
|
|
2008
|
+
if (expression2.kind === "window") {
|
|
2009
|
+
expression2.partitionBy = expression2.partitionBy.map(rewrite);
|
|
2010
|
+
for (const order of expression2.orderBy)
|
|
1975
2011
|
order.expression = rewrite(order.expression);
|
|
1976
|
-
if (
|
|
1977
|
-
|
|
1978
|
-
return
|
|
2012
|
+
if (expression2.argument !== void 0)
|
|
2013
|
+
expression2.argument = rewrite(expression2.argument);
|
|
2014
|
+
return expression2;
|
|
1979
2015
|
}
|
|
1980
|
-
return mapChildExpressions(
|
|
2016
|
+
return mapChildExpressions(expression2, rewrite);
|
|
1981
2017
|
};
|
|
1982
|
-
|
|
2018
|
+
return rewrite(expression);
|
|
2019
|
+
}
|
|
2020
|
+
function replacePlanReferences(block, replacements) {
|
|
2021
|
+
mapBlockExpressions(block, (expression) => replaceExpressionReferences(expression, replacements));
|
|
1983
2022
|
forEachNestedBlock(block, (nested) => replacePlanReferences(nested, replacements));
|
|
1984
2023
|
}
|
|
1985
2024
|
function outerProbeBlock(block, keys, resolvedNames) {
|
|
1986
2025
|
return outerProbeBlockForExpressions(block, keys.map((key) => key.outer), resolvedNames);
|
|
1987
2026
|
}
|
|
1988
2027
|
function outerProbeBlockForExpressions(block, outerExpressions, resolvedNames) {
|
|
2028
|
+
const prefix = outerProbePrefix(block, outerExpressions, resolvedNames);
|
|
2029
|
+
const expressions = outerExpressions.map((expression) => structuredClone(expression));
|
|
2030
|
+
return {
|
|
2031
|
+
sql: "(correlation probes)",
|
|
2032
|
+
base: structuredClone(block.base),
|
|
2033
|
+
joins: structuredClone(block.joins.slice(0, prefix.lastSource)),
|
|
2034
|
+
select: expressions.map((expression, index) => ({
|
|
2035
|
+
expression,
|
|
2036
|
+
alias: correlationProbeAlias(index)
|
|
2037
|
+
})),
|
|
2038
|
+
predicates: probePredicates(block, prefix.retained, prefix.retainsAll),
|
|
2039
|
+
groupBy: expressions,
|
|
2040
|
+
having: [],
|
|
2041
|
+
orderBy: []
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
function outerProbePredicates(block, outerExpressions, resolvedNames) {
|
|
2045
|
+
const sources = /* @__PURE__ */ new Set([block.base.alias, ...block.joins.map((join) => join.alias)]);
|
|
2046
|
+
for (const expression of outerExpressions) {
|
|
2047
|
+
for (const alias of expressionAliases(expression)) {
|
|
2048
|
+
if (!sources.has(alias))
|
|
2049
|
+
return [];
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
const prefix = outerProbePrefix(block, outerExpressions, resolvedNames);
|
|
2053
|
+
return probePredicates(block, prefix.retained, prefix.retainsAll);
|
|
2054
|
+
}
|
|
2055
|
+
function outerProbePrefix(block, outerExpressions, resolvedNames) {
|
|
1989
2056
|
const sources = [block.base, ...block.joins];
|
|
1990
2057
|
const sourceIndexes = outerExpressions.flatMap((expression) => {
|
|
1991
2058
|
const aliases = [...expressionAliases(expression)];
|
|
@@ -1999,22 +2066,9 @@ function outerProbeBlockForExpressions(block, outerExpressions, resolvedNames) {
|
|
|
1999
2066
|
});
|
|
2000
2067
|
});
|
|
2001
2068
|
const lastSource = Math.max(...sourceIndexes);
|
|
2002
|
-
const expressions = outerExpressions.map((expression) => structuredClone(expression));
|
|
2003
2069
|
const retained = new Set(sources.slice(0, lastSource + 1).map((source) => source.alias));
|
|
2004
2070
|
const retainsAll = resolvedNames && lastSource === sources.length - 1;
|
|
2005
|
-
return {
|
|
2006
|
-
sql: "(correlation probes)",
|
|
2007
|
-
base: structuredClone(block.base),
|
|
2008
|
-
joins: structuredClone(block.joins.slice(0, lastSource)),
|
|
2009
|
-
select: expressions.map((expression, index) => ({
|
|
2010
|
-
expression,
|
|
2011
|
-
alias: correlationProbeAlias(index)
|
|
2012
|
-
})),
|
|
2013
|
-
predicates: probePredicates(block, retained, retainsAll),
|
|
2014
|
-
groupBy: expressions,
|
|
2015
|
-
having: [],
|
|
2016
|
-
orderBy: []
|
|
2017
|
-
};
|
|
2071
|
+
return { lastSource, retained, retainsAll };
|
|
2018
2072
|
}
|
|
2019
2073
|
function probePredicates(block, retained, retainsAll) {
|
|
2020
2074
|
return block.predicates.filter((predicate) => probeSafeExpression(predicate.left, retained, retainsAll) && probeSafeExpression(predicate.right, retained, retainsAll)).map((predicate) => structuredClone(predicate));
|
|
@@ -2166,30 +2220,62 @@ function blockReferencesOutside(block) {
|
|
|
2166
2220
|
collectOutsideReferences(block, /* @__PURE__ */ new Set(), refs);
|
|
2167
2221
|
return refs.length > 0;
|
|
2168
2222
|
}
|
|
2169
|
-
function
|
|
2170
|
-
const
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
const parts = expression.reference.split(".");
|
|
2223
|
+
function collectExpressionOutsideReferences(expression, scope, refs) {
|
|
2224
|
+
const visit = (expression2) => {
|
|
2225
|
+
if (expression2.kind === "column") {
|
|
2226
|
+
const parts = expression2.reference.split(".");
|
|
2174
2227
|
const alias = parts[0];
|
|
2175
2228
|
if (parts.length === 2 && alias !== void 0 && !scope.has(alias)) {
|
|
2176
|
-
refs.push(
|
|
2229
|
+
refs.push(expression2.reference);
|
|
2177
2230
|
}
|
|
2178
2231
|
return;
|
|
2179
2232
|
}
|
|
2180
|
-
if (
|
|
2181
|
-
collectOutsideReferences(
|
|
2233
|
+
if (expression2.kind === "subquery" || expression2.kind === "exists") {
|
|
2234
|
+
collectOutsideReferences(expression2.block, scope, refs);
|
|
2182
2235
|
return;
|
|
2183
2236
|
}
|
|
2184
|
-
for (const child of childExpressions(
|
|
2237
|
+
for (const child of childExpressions(expression2))
|
|
2185
2238
|
visit(child);
|
|
2186
2239
|
};
|
|
2187
|
-
|
|
2240
|
+
visit(expression);
|
|
2241
|
+
}
|
|
2242
|
+
function collectOutsideReferences(block, outer, refs) {
|
|
2243
|
+
const scope = /* @__PURE__ */ new Set([...outer, block.base.alias, ...block.joins.map((join) => join.alias)]);
|
|
2244
|
+
forEachBlockExpression(block, (expression) => collectExpressionOutsideReferences(expression, scope, refs));
|
|
2188
2245
|
forEachNestedBlock(block, (nested) => {
|
|
2189
2246
|
collectOutsideReferences(nested, scope, refs);
|
|
2190
2247
|
});
|
|
2191
2248
|
}
|
|
2249
|
+
function probeOuterReferences(block, additional, scope, label) {
|
|
2250
|
+
const outside = [];
|
|
2251
|
+
collectOutsideReferences(block, /* @__PURE__ */ new Set(), outside);
|
|
2252
|
+
const references = [.../* @__PURE__ */ new Set([...outside, ...additional])];
|
|
2253
|
+
if (references.length === 0) {
|
|
2254
|
+
throw new TypeError(`Correlated ${label} subquery has no usable outer reference`);
|
|
2255
|
+
}
|
|
2256
|
+
for (const reference of references) {
|
|
2257
|
+
const parts = reference.split(".");
|
|
2258
|
+
const alias = parts.length === 2 ? parts[0] : void 0;
|
|
2259
|
+
if (alias === void 0 || !scope.has(alias)) {
|
|
2260
|
+
throw new TypeError(`Correlated ${label} references an unavailable outer source: ${reference}`);
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
return references;
|
|
2264
|
+
}
|
|
2192
2265
|
function extractCorrelation(inner, outerScope, label, comparisons = false) {
|
|
2266
|
+
const keys = takeCorrelationPredicates(inner, outerScope, comparisons);
|
|
2267
|
+
const leftover = [];
|
|
2268
|
+
collectOutsideReferences(inner, /* @__PURE__ */ new Set(), leftover);
|
|
2269
|
+
if (leftover.length > 0) {
|
|
2270
|
+
const supported = comparisons ? "comparison" : "equality";
|
|
2271
|
+
throw new TypeError(`Correlated ${label} subqueries support only ${supported} conditions between one inner and one outer qualified column (unsupported reference: ${leftover[0] ?? ""})`);
|
|
2272
|
+
}
|
|
2273
|
+
if (keys.length === 0) {
|
|
2274
|
+
throw new TypeError(`Correlated ${label} subquery has no usable correlation condition`);
|
|
2275
|
+
}
|
|
2276
|
+
return keys;
|
|
2277
|
+
}
|
|
2278
|
+
function takeCorrelationPredicates(inner, outerScope, comparisons) {
|
|
2193
2279
|
const innerScope = /* @__PURE__ */ new Set([inner.base.alias, ...inner.joins.map((join) => join.alias)]);
|
|
2194
2280
|
const keys = [];
|
|
2195
2281
|
const kept = [];
|
|
@@ -2201,15 +2287,6 @@ function extractCorrelation(inner, outerScope, label, comparisons = false) {
|
|
|
2201
2287
|
keys.push(key);
|
|
2202
2288
|
}
|
|
2203
2289
|
inner.predicates = kept;
|
|
2204
|
-
const leftover = [];
|
|
2205
|
-
collectOutsideReferences(inner, /* @__PURE__ */ new Set(), leftover);
|
|
2206
|
-
if (leftover.length > 0) {
|
|
2207
|
-
const supported = comparisons ? "comparison" : "equality";
|
|
2208
|
-
throw new TypeError(`Correlated ${label} subqueries support only ${supported} conditions between one inner and one outer qualified column (unsupported reference: ${leftover[0] ?? ""})`);
|
|
2209
|
-
}
|
|
2210
|
-
if (keys.length === 0) {
|
|
2211
|
-
throw new TypeError(`Correlated ${label} subquery has no usable correlation condition`);
|
|
2212
|
-
}
|
|
2213
2290
|
return keys;
|
|
2214
2291
|
}
|
|
2215
2292
|
function canExtractCorrelation(inner, outerScope, label, comparisons) {
|
|
@@ -2328,14 +2405,15 @@ function foldExpression(expression) {
|
|
|
2328
2405
|
const left = foldExpression(expression.left);
|
|
2329
2406
|
const right = foldExpression(expression.right);
|
|
2330
2407
|
if (left.kind === "literal" && right.kind === "literal") {
|
|
2331
|
-
const folded = foldBinary(expression.operator, left.value, right.value);
|
|
2408
|
+
const folded = foldBinary(expression.operator, left.value, right.value, expression.integer === true);
|
|
2332
2409
|
if (folded !== void 0) {
|
|
2333
2410
|
const domain = left.sqlDomain?.kind === "numeric" || right.sqlDomain?.kind === "numeric" ? { kind: "numeric" } : void 0;
|
|
2334
2411
|
return {
|
|
2335
2412
|
kind: "literal",
|
|
2336
2413
|
value: folded,
|
|
2337
2414
|
...isSqlDomainValue(folded) ? { internalSqlValue: true } : {},
|
|
2338
|
-
...domain === void 0 ? {} : { sqlDomain: domain }
|
|
2415
|
+
...domain === void 0 ? {} : { sqlDomain: domain },
|
|
2416
|
+
...left.decimal === true || right.decimal === true ? { decimal: true } : {}
|
|
2339
2417
|
};
|
|
2340
2418
|
}
|
|
2341
2419
|
}
|
|
@@ -2407,7 +2485,7 @@ function foldExpression(expression) {
|
|
|
2407
2485
|
}
|
|
2408
2486
|
return expression;
|
|
2409
2487
|
}
|
|
2410
|
-
function foldBinary(operator, leftValue, rightValue) {
|
|
2488
|
+
function foldBinary(operator, leftValue, rightValue, integer = false) {
|
|
2411
2489
|
if (leftValue === null || rightValue === null)
|
|
2412
2490
|
return null;
|
|
2413
2491
|
if (operator === "||") {
|
|
@@ -2428,6 +2506,9 @@ function foldBinary(operator, leftValue, rightValue) {
|
|
|
2428
2506
|
const right = rightValue instanceof Date ? dateMilliseconds(rightValue) : rightValue;
|
|
2429
2507
|
if ((operator === "/" || operator === "%") && right === 0)
|
|
2430
2508
|
return null;
|
|
2509
|
+
if (operator === "/" && integer && typeof left === "number" && typeof right === "number") {
|
|
2510
|
+
return integerQuotient(left, right);
|
|
2511
|
+
}
|
|
2431
2512
|
const result = operator === "+" ? left + right : operator === "-" ? left - right : operator === "*" ? left * right : operator === "%" ? left % right : left / right;
|
|
2432
2513
|
return Number.isFinite(result) ? result : void 0;
|
|
2433
2514
|
}
|
|
@@ -2449,7 +2530,7 @@ function pushPredicatesIntoDerived(block) {
|
|
|
2449
2530
|
function pushdownTarget(predicate, sources, singleSource) {
|
|
2450
2531
|
for (const source of sources) {
|
|
2451
2532
|
const derived = source.derived;
|
|
2452
|
-
if (derived === void 0 || derived
|
|
2533
|
+
if (derived === void 0 || blockHasRowWindow(derived))
|
|
2453
2534
|
continue;
|
|
2454
2535
|
const left = rewriteForInner(predicate.left, source, derived, singleSource);
|
|
2455
2536
|
if (left === void 0)
|
|
@@ -2681,7 +2762,7 @@ function referencedOutputAliases(block, source, singleSource) {
|
|
|
2681
2762
|
}
|
|
2682
2763
|
function combineDerivedLimit(block) {
|
|
2683
2764
|
const derived = block.base.derived;
|
|
2684
|
-
if (derived === void 0 || block.joins.length > 0 || block.predicates.length > 0 || block.groupBy.length > 0 || block.having.length > 0 || block.orderBy.length > 0 || block.limit === void 0 || block.offset !== void 0 || derived.offset !== void 0) {
|
|
2765
|
+
if (derived === void 0 || block.joins.length > 0 || block.predicates.length > 0 || block.groupBy.length > 0 || block.having.length > 0 || block.orderBy.length > 0 || block.limit === void 0 || block.offset !== void 0 || block.offsetParameter !== void 0 || derived.offset !== void 0 || derived.offsetParameter !== void 0 || derived.limitParameter !== void 0) {
|
|
2685
2766
|
return;
|
|
2686
2767
|
}
|
|
2687
2768
|
derived.limit = Math.min(derived.limit ?? Number.MAX_SAFE_INTEGER, block.limit);
|
package/dist/engine/query.d.ts
CHANGED
|
@@ -135,6 +135,13 @@ export type InsertValue = QueryValue | DefaultInsertValue | DeferredInsertExpres
|
|
|
135
135
|
};
|
|
136
136
|
export declare function isDefaultInsertValue(value: InsertValue): value is DefaultInsertValue;
|
|
137
137
|
export declare function isDeferredInsertExpression(value: InsertValue): value is DeferredInsertExpression;
|
|
138
|
+
/** The FROM / USING sources of an UPDATE or DELETE, joined to the target as a cross product. */
|
|
139
|
+
export interface MutationSources {
|
|
140
|
+
base: TableSource;
|
|
141
|
+
joins: JoinPlan[];
|
|
142
|
+
/** ON conditions of parenthesized join groups, which filter the product like WHERE. */
|
|
143
|
+
predicates: Predicate[];
|
|
144
|
+
}
|
|
138
145
|
export type CompiledStatement = {
|
|
139
146
|
kind: "select";
|
|
140
147
|
sql: string;
|
|
@@ -189,6 +196,12 @@ export type CompiledStatement = {
|
|
|
189
196
|
operator: PredicateOperator;
|
|
190
197
|
right: Expression;
|
|
191
198
|
}>;
|
|
199
|
+
/**
|
|
200
|
+
* `UPDATE t SET … FROM sources WHERE …`: the extra sources the assignments and predicates
|
|
201
|
+
* may read, joined to the target as PostgreSQL joins them. A target row matched by several
|
|
202
|
+
* source rows is updated once, from the first match.
|
|
203
|
+
*/
|
|
204
|
+
from?: MutationSources;
|
|
192
205
|
returning?: string[] | "*";
|
|
193
206
|
/** RETURNING items that are not plain columns, evaluated over the affected rows. */
|
|
194
207
|
returningItems?: ReturningItem[];
|
|
@@ -202,6 +215,8 @@ export type CompiledStatement = {
|
|
|
202
215
|
operator: PredicateOperator;
|
|
203
216
|
right: Expression;
|
|
204
217
|
}>;
|
|
218
|
+
/** `DELETE FROM t USING sources WHERE …`: the extra sources the predicates may read. */
|
|
219
|
+
from?: MutationSources;
|
|
205
220
|
returning?: string[] | "*";
|
|
206
221
|
/** RETURNING items that are not plain columns, evaluated over the affected rows. */
|
|
207
222
|
returningItems?: ReturningItem[];
|
|
@@ -303,6 +318,19 @@ export type CompiledStatement = {
|
|
|
303
318
|
view: string;
|
|
304
319
|
ifExists?: boolean;
|
|
305
320
|
parameterCount?: number;
|
|
321
|
+
}
|
|
322
|
+
/** A session setting, accepted and ignored: `SET name TO value`, `RESET name`. */
|
|
323
|
+
| {
|
|
324
|
+
kind: "set";
|
|
325
|
+
action: "set" | "reset";
|
|
326
|
+
name: string;
|
|
327
|
+
parameterCount?: number;
|
|
328
|
+
}
|
|
329
|
+
/** `SHOW name`: the engine's value for a PostgreSQL setting, as one row. */
|
|
330
|
+
| {
|
|
331
|
+
kind: "show";
|
|
332
|
+
name: string;
|
|
333
|
+
parameterCount?: number;
|
|
306
334
|
} | {
|
|
307
335
|
kind: "transaction";
|
|
308
336
|
action: "begin" | "commit" | "rollback" | "savepoint" | "rollback-to" | "release";
|
|
@@ -487,6 +515,64 @@ export declare function mapChildExpressions(expression: Expression, map: (child:
|
|
|
487
515
|
export declare function forEachBlockExpression(block: CompiledQuery, visit: (expression: Expression) => void): void;
|
|
488
516
|
/** Visits every nested block of one block's sources (derived, union, windowed, recursive). */
|
|
489
517
|
export declare function forEachNestedBlock(block: CompiledQuery, visit: (nested: CompiledQuery) => void): void;
|
|
518
|
+
/**
|
|
519
|
+
* Whether an expression is integer-typed the way PostgreSQL types it: an INTEGER, BIGINT, or
|
|
520
|
+
* SMALLINT column, an integer constant, COUNT, an integer CAST, SUM/MIN/MAX/ABS over an
|
|
521
|
+
* integer, MOD over integers, a CASE/COALESCE/NULLIF/GREATEST/LEAST whose every branch is an
|
|
522
|
+
* integer, or `+ - * / %` over two integers. A bound parameter takes the type of its integer
|
|
523
|
+
* partner, as PostgreSQL infers `qty / $1`. A column the caller cannot type is not an integer,
|
|
524
|
+
* so the compiler's static answer only ever widens once the catalog is known.
|
|
525
|
+
*/
|
|
526
|
+
export declare function integerTypedExpression(expression: Expression, columnInteger?: (reference: string) => boolean, tableIntegerColumns?: (table: string) => IntegerColumns | undefined): boolean;
|
|
527
|
+
/** A table's integer columns, or `true` when every column may be one (the bind-time probe). */
|
|
528
|
+
export type IntegerColumns = ReadonlySet<string> | true;
|
|
529
|
+
/** PostgreSQL's integer `/`: truncation toward zero, NULL for a zero divisor. */
|
|
530
|
+
export declare function integerQuotient(left: number, right: number): number | null;
|
|
531
|
+
/**
|
|
532
|
+
* Marks every `/` in one expression whose operands are integers, subquery blocks included, so
|
|
533
|
+
* that every evaluator truncates it the way PostgreSQL does. Children are marked first: the
|
|
534
|
+
* parent's typing reads the children, and `(a / b) / c` is integer only once `a / b` is.
|
|
535
|
+
*/
|
|
536
|
+
export declare function annotateIntegerDivision(expression: Expression, columnInteger?: (reference: string) => boolean, tableIntegerColumns?: (table: string) => IntegerColumns | undefined): void;
|
|
537
|
+
/**
|
|
538
|
+
* Marks integer division throughout a plan, nested blocks first. Without a catalog only
|
|
539
|
+
* constants, COUNT, and integer CASTs type as integers; with one, INTEGER columns do too, and
|
|
540
|
+
* a derived source's outputs follow from its own select list.
|
|
541
|
+
*/
|
|
542
|
+
export declare function annotatePlanIntegerDivision(plan: CompiledQuery, tableIntegerColumns?: (table: string) => IntegerColumns | undefined): void;
|
|
543
|
+
/**
|
|
544
|
+
* Whether catalog binding could still mark a division in this plan: a `/` not yet marked whose
|
|
545
|
+
* operands would be integers if every column were one. Plans without such a node — the common
|
|
546
|
+
* case — need no clone at bind time.
|
|
547
|
+
*/
|
|
548
|
+
export declare function planMayHaveIntegerDivision(plan: CompiledQuery): boolean;
|
|
549
|
+
/**
|
|
550
|
+
* PostgreSQL folds an unquoted identifier to lower case, so `SELECT ID FROM USERS` reads the
|
|
551
|
+
* table `users` and its column `id`. Minnow keeps the written spelling and resolves it exactly,
|
|
552
|
+
* which is what a catalog created through the typed API (where `Orders` is a legal name)
|
|
553
|
+
* relies on. This pass gives both: a table or column name that resolves exactly is left alone,
|
|
554
|
+
* and one that does not is read as the unique catalog name that matches it case-insensitively.
|
|
555
|
+
* Only plain tables and their columns are folded; derived sources expose their aliases as
|
|
556
|
+
* written. Returns the same plan when nothing needed folding, so cached plans stay untouched.
|
|
557
|
+
*/
|
|
558
|
+
export declare function foldIdentifierCase(plan: CompiledQuery, tables: ReadonlyMap<string, readonly string[]>): CompiledQuery;
|
|
559
|
+
/**
|
|
560
|
+
* A table alias used as a value — `json_agg(agg)`, `to_json(obj)`, `row_to_json(u)` — is the
|
|
561
|
+
* row as a JSON object in PostgreSQL. The reference is expanded here, once the catalog is
|
|
562
|
+
* known, into JSON_OBJECT over the source's columns in declaration order, so the aggregate and
|
|
563
|
+
* constructor paths see a scalar document. Only an alias of the same block that names no column
|
|
564
|
+
* expands; a bare name that is also a column stays that column.
|
|
565
|
+
*/
|
|
566
|
+
export declare function expandRowReferences(plan: CompiledQuery, tableColumns: (table: string) => readonly string[] | undefined): CompiledQuery;
|
|
567
|
+
/**
|
|
568
|
+
* PostgreSQL's functional-dependency rule (SQL:2003 T301): once a table's whole primary key is
|
|
569
|
+
* in GROUP BY, any other column of that table may appear ungrouped, because the key determines
|
|
570
|
+
* the row. Minnow's executors group by the listed expressions only, so such a column is added
|
|
571
|
+
* to GROUP BY here — the groups are unchanged, since the key already separates every row — and
|
|
572
|
+
* the containment check then accepts it. Only plain table sources with a declared key
|
|
573
|
+
* qualify; a derived table exposes no key. Returns the same plan when nothing needed adding.
|
|
574
|
+
*/
|
|
575
|
+
export declare function extendGroupByWithKeyDependents(plan: CompiledQuery, keyColumns: (table: string) => readonly string[] | undefined, tableColumns?: (table: string) => readonly string[] | undefined): CompiledQuery;
|
|
490
576
|
/**
|
|
491
577
|
* Rewrites every expression slot of one block in place, the writing counterpart to
|
|
492
578
|
* `forEachBlockExpression`. The mapper receives each root expression and returns its
|
|
@@ -650,6 +736,11 @@ interface SelectBlockParts {
|
|
|
650
736
|
/** GROUP BY GROUPING SETS/ROLLUP/CUBE: the grouping lists to union; groupBy is then unused. */
|
|
651
737
|
groupingSets?: Expression[][];
|
|
652
738
|
}
|
|
739
|
+
/**
|
|
740
|
+
* The execution-time half of unifyGroupedReferences for a block over several sources: an
|
|
741
|
+
* unqualified name is the column of the one source that has it, which only the schemas know.
|
|
742
|
+
*/
|
|
743
|
+
export declare function unifyPlanGroupedReferences(plan: CompiledQuery, columnsOf: (tableName: string) => readonly string[] | undefined): CompiledQuery;
|
|
653
744
|
export declare function assembleSelectBlock(parts: SelectBlockParts, nextSequence: () => number): CompiledQuery;
|
|
654
745
|
/**
|
|
655
746
|
* Turns every source carrying a column alias list into a derived projection that renames the
|
|
@@ -688,6 +779,13 @@ export declare function expandNaturalJoins(plan: CompiledQuery, columnsOf: (tabl
|
|
|
688
779
|
export declare function expandSourceColumnAliases(plan: CompiledQuery, columnsOf: (tableName: string) => readonly string[] | undefined): CompiledQuery;
|
|
689
780
|
/** Wraps compound members into the set-operation source the executor folds left to right. */
|
|
690
781
|
export declare function compoundSelectBlock(sql: string, blocks: CompiledQuery[], ops: SetOperator[], tail: SelectTail, nextSequence: () => number): CompiledQuery;
|
|
782
|
+
/**
|
|
783
|
+
* True when a block trims its rows with LIMIT, OFFSET, or a placeholder for either. A rewrite
|
|
784
|
+
* that moves a filter or a limit across such a block changes which rows the window sees, so every
|
|
785
|
+
* "does this block page its rows" guard goes through here: reading only the literal fields lets
|
|
786
|
+
* `LIMIT ?`, `OFFSET ?`, and a bare `OFFSET n` slip past and the predicate run before the window.
|
|
787
|
+
*/
|
|
788
|
+
export declare function blockHasRowWindow(block: CompiledQuery): boolean;
|
|
691
789
|
/**
|
|
692
790
|
* Detects a pure projection wrapper over one derived block — the shape the ORDER-BY-expression
|
|
693
791
|
* desugar emits: no joins, filters, grouping, ordering, or paging of its own, and every select
|