@prisma/client-engine-runtime 7.10.0-dev.21 → 7.10.0-dev.22
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/index.d.mts +123 -91
- package/dist/index.d.ts +123 -91
- package/dist/index.js +254 -20
- package/dist/index.mjs +254 -20
- package/dist/interpreter/query-interpreter.d.ts +20 -1
- package/dist/query-plan.d.ts +61 -31
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -1623,17 +1623,27 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1623
1623
|
});
|
|
1624
1624
|
}
|
|
1625
1625
|
async run(queryPlan, options) {
|
|
1626
|
-
const
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1626
|
+
const generators = this.#generators.snapshot();
|
|
1627
|
+
const context = { ...options, generators };
|
|
1628
|
+
const purified = purifyQueryPlan(queryPlan, (node) => this.interpretNode(node, context))?.catch(
|
|
1629
|
+
(e) => rethrowAsUserFacing(e)
|
|
1630
|
+
);
|
|
1631
|
+
if (purified) {
|
|
1632
|
+
try {
|
|
1633
|
+
return this.#interpretPureNode(await purified, context.scope, generators).value;
|
|
1634
|
+
} catch (e) {
|
|
1635
|
+
rethrowAsUserFacing(e);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
const { value } = await this.interpretNode(queryPlan, context).catch((e) => rethrowAsUserFacing(e));
|
|
1630
1639
|
return value;
|
|
1631
1640
|
}
|
|
1632
1641
|
async interpretNode(node, context) {
|
|
1633
1642
|
switch (node.type) {
|
|
1634
1643
|
case "value": {
|
|
1635
1644
|
return {
|
|
1636
|
-
value: evaluateArg(node.args, context.scope, context.generators)
|
|
1645
|
+
value: evaluateArg(node.args, context.scope, context.generators),
|
|
1646
|
+
lastInsertId: node.lastInsertId
|
|
1637
1647
|
};
|
|
1638
1648
|
}
|
|
1639
1649
|
case "seq": {
|
|
@@ -1643,9 +1653,6 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1643
1653
|
}
|
|
1644
1654
|
return result ?? { value: void 0 };
|
|
1645
1655
|
}
|
|
1646
|
-
case "get": {
|
|
1647
|
-
return { value: context.scope[node.args.name] };
|
|
1648
|
-
}
|
|
1649
1656
|
case "let": {
|
|
1650
1657
|
const nestedScope = Object.create(context.scope);
|
|
1651
1658
|
for (const binding of node.args.bindings) {
|
|
@@ -1654,15 +1661,6 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1654
1661
|
}
|
|
1655
1662
|
return this.interpretNode(node.args.expr, { ...context, scope: nestedScope });
|
|
1656
1663
|
}
|
|
1657
|
-
case "getFirstNonEmpty": {
|
|
1658
|
-
for (const name of node.args.names) {
|
|
1659
|
-
const value = context.scope[name];
|
|
1660
|
-
if (!isEmpty(value)) {
|
|
1661
|
-
return { value };
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
return { value: [] };
|
|
1665
|
-
}
|
|
1666
1664
|
case "concat": {
|
|
1667
1665
|
const parts = await Promise.all(
|
|
1668
1666
|
node.args.map((arg) => this.interpretNode(arg, context).then((res) => res.value))
|
|
@@ -1789,9 +1787,6 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1789
1787
|
return await this.interpretNode(node.args.else, context);
|
|
1790
1788
|
}
|
|
1791
1789
|
}
|
|
1792
|
-
case "unit": {
|
|
1793
|
-
return { value: void 0 };
|
|
1794
|
-
}
|
|
1795
1790
|
case "diff": {
|
|
1796
1791
|
const { value: from } = await this.interpretNode(node.args.from, context);
|
|
1797
1792
|
const { value: to } = await this.interpretNode(node.args.to, context);
|
|
@@ -1821,6 +1816,139 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1821
1816
|
}
|
|
1822
1817
|
return { value: record, lastInsertId };
|
|
1823
1818
|
}
|
|
1819
|
+
default:
|
|
1820
|
+
return this.#interpretPureNode(node, context.scope, context.generators);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
#interpretPureNode(node, scope, generators) {
|
|
1824
|
+
switch (node.type) {
|
|
1825
|
+
case "value": {
|
|
1826
|
+
return { value: evaluateArg(node.args, scope, generators), lastInsertId: node.lastInsertId };
|
|
1827
|
+
}
|
|
1828
|
+
case "seq": {
|
|
1829
|
+
let result;
|
|
1830
|
+
for (const arg of node.args) {
|
|
1831
|
+
result = this.#interpretPureNode(arg, scope, generators);
|
|
1832
|
+
}
|
|
1833
|
+
return result ?? { value: void 0 };
|
|
1834
|
+
}
|
|
1835
|
+
case "get": {
|
|
1836
|
+
return { value: scope[node.args.name] };
|
|
1837
|
+
}
|
|
1838
|
+
case "let": {
|
|
1839
|
+
const nestedScope = Object.create(scope);
|
|
1840
|
+
for (const binding of node.args.bindings) {
|
|
1841
|
+
const { value } = this.#interpretPureNode(binding.expr, nestedScope, generators);
|
|
1842
|
+
nestedScope[binding.name] = value;
|
|
1843
|
+
}
|
|
1844
|
+
return this.#interpretPureNode(node.args.expr, nestedScope, generators);
|
|
1845
|
+
}
|
|
1846
|
+
case "getFirstNonEmpty": {
|
|
1847
|
+
for (const name of node.args.names) {
|
|
1848
|
+
const value = scope[name];
|
|
1849
|
+
if (!isEmpty(value)) {
|
|
1850
|
+
return { value };
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
return { value: [] };
|
|
1854
|
+
}
|
|
1855
|
+
case "concat": {
|
|
1856
|
+
const parts = node.args.map((arg) => this.#interpretPureNode(arg, scope, generators).value);
|
|
1857
|
+
return {
|
|
1858
|
+
value: parts.length > 0 ? parts.reduce((acc, part) => acc.concat(asList(part)), []) : []
|
|
1859
|
+
};
|
|
1860
|
+
}
|
|
1861
|
+
case "sum": {
|
|
1862
|
+
const parts = node.args.map((arg) => this.#interpretPureNode(arg, scope, generators).value);
|
|
1863
|
+
return {
|
|
1864
|
+
value: parts.length > 0 ? parts.reduce((acc, part) => asNumber2(acc) + asNumber2(part)) : 0
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
case "reverse": {
|
|
1868
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
|
|
1869
|
+
return { value: Array.isArray(value) ? value.reverse() : value, lastInsertId };
|
|
1870
|
+
}
|
|
1871
|
+
case "unique": {
|
|
1872
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
|
|
1873
|
+
if (!Array.isArray(value)) {
|
|
1874
|
+
return { value, lastInsertId };
|
|
1875
|
+
}
|
|
1876
|
+
if (value.length > 1) {
|
|
1877
|
+
throw new Error(`Expected zero or one element, got ${value.length}`);
|
|
1878
|
+
}
|
|
1879
|
+
return { value: value[0] ?? null, lastInsertId };
|
|
1880
|
+
}
|
|
1881
|
+
case "required": {
|
|
1882
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
|
|
1883
|
+
if (isEmpty(value)) {
|
|
1884
|
+
throw new Error("Required value is empty");
|
|
1885
|
+
}
|
|
1886
|
+
return { value, lastInsertId };
|
|
1887
|
+
}
|
|
1888
|
+
case "mapField": {
|
|
1889
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args.records, scope, generators);
|
|
1890
|
+
return { value: mapField2(value, node.args.field), lastInsertId };
|
|
1891
|
+
}
|
|
1892
|
+
case "join": {
|
|
1893
|
+
const { value: parent, lastInsertId } = this.#interpretPureNode(node.args.parent, scope, generators);
|
|
1894
|
+
if (parent === null) {
|
|
1895
|
+
return { value: null, lastInsertId };
|
|
1896
|
+
}
|
|
1897
|
+
const children = node.args.children.map((joinExpr) => ({
|
|
1898
|
+
joinExpr,
|
|
1899
|
+
childRecords: this.#interpretPureNode(joinExpr.child, scope, generators).value
|
|
1900
|
+
}));
|
|
1901
|
+
return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
|
|
1902
|
+
}
|
|
1903
|
+
case "dataMap": {
|
|
1904
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
|
|
1905
|
+
return { value: applyDataMap(value, node.args.structure, node.args.enums), lastInsertId };
|
|
1906
|
+
}
|
|
1907
|
+
case "validate": {
|
|
1908
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
|
|
1909
|
+
performValidation(value, node.args.rules, node.args);
|
|
1910
|
+
return { value, lastInsertId };
|
|
1911
|
+
}
|
|
1912
|
+
case "if": {
|
|
1913
|
+
const { value } = this.#interpretPureNode(node.args.value, scope, generators);
|
|
1914
|
+
if (doesSatisfyRule(value, node.args.rule)) {
|
|
1915
|
+
return this.#interpretPureNode(node.args.then, scope, generators);
|
|
1916
|
+
} else {
|
|
1917
|
+
return this.#interpretPureNode(node.args.else, scope, generators);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
case "unit": {
|
|
1921
|
+
return { value: void 0 };
|
|
1922
|
+
}
|
|
1923
|
+
case "diff": {
|
|
1924
|
+
const { value: from } = this.#interpretPureNode(node.args.from, scope, generators);
|
|
1925
|
+
const { value: to } = this.#interpretPureNode(node.args.to, scope, generators);
|
|
1926
|
+
const keyGetter = (item) => item !== null ? getRecordKey(asRecord(item), node.args.fields) : null;
|
|
1927
|
+
const toSet = new Set(asList(to).map(keyGetter));
|
|
1928
|
+
return { value: asList(from).filter((item) => !toSet.has(keyGetter(item))) };
|
|
1929
|
+
}
|
|
1930
|
+
case "process": {
|
|
1931
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
|
|
1932
|
+
const ops = cloneObject(node.args.operations);
|
|
1933
|
+
evaluateProcessingParameters(ops, scope, generators);
|
|
1934
|
+
return { value: processRecords(value, ops), lastInsertId };
|
|
1935
|
+
}
|
|
1936
|
+
case "initializeRecord": {
|
|
1937
|
+
const { lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
|
|
1938
|
+
const record = {};
|
|
1939
|
+
for (const [key, initializer] of Object.entries(node.args.fields)) {
|
|
1940
|
+
record[key] = evalFieldInitializer(initializer, lastInsertId, scope, generators);
|
|
1941
|
+
}
|
|
1942
|
+
return { value: record, lastInsertId };
|
|
1943
|
+
}
|
|
1944
|
+
case "mapRecord": {
|
|
1945
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
|
|
1946
|
+
const record = value === null ? {} : asRecord(value);
|
|
1947
|
+
for (const [key, entry] of Object.entries(node.args.fields)) {
|
|
1948
|
+
record[key] = evalFieldOperation(entry, record[key], scope, generators);
|
|
1949
|
+
}
|
|
1950
|
+
return { value: record, lastInsertId };
|
|
1951
|
+
}
|
|
1824
1952
|
default:
|
|
1825
1953
|
assertNever(node, `Unexpected node type: ${node.type}`);
|
|
1826
1954
|
}
|
|
@@ -1998,6 +2126,112 @@ function evalFieldOperation(op, value, scope, generators) {
|
|
|
1998
2126
|
assertNever(op, `Unexpected field operation type: ${op["type"]}`);
|
|
1999
2127
|
}
|
|
2000
2128
|
}
|
|
2129
|
+
function purifyQueryPlan(node, evalNode) {
|
|
2130
|
+
const impureNode = findUniqueUnconditionalImpureNode(node);
|
|
2131
|
+
if (!impureNode) {
|
|
2132
|
+
return void 0;
|
|
2133
|
+
}
|
|
2134
|
+
return evalNode(impureNode).then((result) => {
|
|
2135
|
+
const evaluated = {
|
|
2136
|
+
type: "value",
|
|
2137
|
+
args: result.value,
|
|
2138
|
+
lastInsertId: result.lastInsertId
|
|
2139
|
+
};
|
|
2140
|
+
const purified = replaceImpureNode(node, impureNode, evaluated);
|
|
2141
|
+
if (!purified) {
|
|
2142
|
+
throw new Error("Could not substitute the evaluated impure node into the query plan");
|
|
2143
|
+
}
|
|
2144
|
+
return purified;
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
function replaceImpureNode(node, target, replacement) {
|
|
2148
|
+
if (node === target) {
|
|
2149
|
+
return replacement;
|
|
2150
|
+
}
|
|
2151
|
+
switch (node.type) {
|
|
2152
|
+
case "seq":
|
|
2153
|
+
case "sum":
|
|
2154
|
+
case "concat": {
|
|
2155
|
+
for (let i = 0; i < node.args.length; i++) {
|
|
2156
|
+
const child = replaceImpureNode(node.args[i], target, replacement);
|
|
2157
|
+
if (child) {
|
|
2158
|
+
return { ...node, args: node.args.map((arg, j) => j === i ? child : arg) };
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
return void 0;
|
|
2162
|
+
}
|
|
2163
|
+
case "dataMap":
|
|
2164
|
+
case "validate":
|
|
2165
|
+
case "initializeRecord":
|
|
2166
|
+
case "mapRecord":
|
|
2167
|
+
case "process": {
|
|
2168
|
+
const expr = replaceImpureNode(node.args.expr, target, replacement);
|
|
2169
|
+
return expr && { ...node, args: { ...node.args, expr } };
|
|
2170
|
+
}
|
|
2171
|
+
case "mapField": {
|
|
2172
|
+
const records = replaceImpureNode(node.args.records, target, replacement);
|
|
2173
|
+
return records && { ...node, args: { ...node.args, records } };
|
|
2174
|
+
}
|
|
2175
|
+
case "reverse":
|
|
2176
|
+
case "unique":
|
|
2177
|
+
case "required": {
|
|
2178
|
+
const args = replaceImpureNode(node.args, target, replacement);
|
|
2179
|
+
return args && { ...node, args };
|
|
2180
|
+
}
|
|
2181
|
+
default:
|
|
2182
|
+
return void 0;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
function findUniqueUnconditionalImpureNode(node) {
|
|
2186
|
+
switch (node.type) {
|
|
2187
|
+
case "query":
|
|
2188
|
+
case "execute":
|
|
2189
|
+
return node;
|
|
2190
|
+
case "seq":
|
|
2191
|
+
case "sum":
|
|
2192
|
+
case "concat": {
|
|
2193
|
+
let found = void 0;
|
|
2194
|
+
for (const child of node.args) {
|
|
2195
|
+
const childFound = findUniqueUnconditionalImpureNode(child);
|
|
2196
|
+
if (childFound === null) {
|
|
2197
|
+
return null;
|
|
2198
|
+
}
|
|
2199
|
+
if (childFound) {
|
|
2200
|
+
if (found) {
|
|
2201
|
+
return null;
|
|
2202
|
+
}
|
|
2203
|
+
found = childFound;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
return found;
|
|
2207
|
+
}
|
|
2208
|
+
case "dataMap":
|
|
2209
|
+
case "validate":
|
|
2210
|
+
case "initializeRecord":
|
|
2211
|
+
case "mapRecord":
|
|
2212
|
+
case "process":
|
|
2213
|
+
return findUniqueUnconditionalImpureNode(node.args.expr);
|
|
2214
|
+
case "mapField":
|
|
2215
|
+
return findUniqueUnconditionalImpureNode(node.args.records);
|
|
2216
|
+
case "reverse":
|
|
2217
|
+
case "unique":
|
|
2218
|
+
case "required":
|
|
2219
|
+
return findUniqueUnconditionalImpureNode(node.args);
|
|
2220
|
+
case "let":
|
|
2221
|
+
case "join":
|
|
2222
|
+
case "diff":
|
|
2223
|
+
case "if":
|
|
2224
|
+
case "transaction":
|
|
2225
|
+
return null;
|
|
2226
|
+
case "value":
|
|
2227
|
+
case "get":
|
|
2228
|
+
case "getFirstNonEmpty":
|
|
2229
|
+
case "unit":
|
|
2230
|
+
return void 0;
|
|
2231
|
+
default:
|
|
2232
|
+
assertNever(node, `Unexpected node type: ${node.type}`);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2001
2235
|
function applyComments(query, sqlCommenter) {
|
|
2002
2236
|
if (!sqlCommenter || sqlCommenter.plugins.length === 0) {
|
|
2003
2237
|
return query;
|
package/dist/index.mjs
CHANGED
|
@@ -1570,17 +1570,27 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1570
1570
|
});
|
|
1571
1571
|
}
|
|
1572
1572
|
async run(queryPlan, options) {
|
|
1573
|
-
const
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1573
|
+
const generators = this.#generators.snapshot();
|
|
1574
|
+
const context = { ...options, generators };
|
|
1575
|
+
const purified = purifyQueryPlan(queryPlan, (node) => this.interpretNode(node, context))?.catch(
|
|
1576
|
+
(e) => rethrowAsUserFacing(e)
|
|
1577
|
+
);
|
|
1578
|
+
if (purified) {
|
|
1579
|
+
try {
|
|
1580
|
+
return this.#interpretPureNode(await purified, context.scope, generators).value;
|
|
1581
|
+
} catch (e) {
|
|
1582
|
+
rethrowAsUserFacing(e);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
const { value } = await this.interpretNode(queryPlan, context).catch((e) => rethrowAsUserFacing(e));
|
|
1577
1586
|
return value;
|
|
1578
1587
|
}
|
|
1579
1588
|
async interpretNode(node, context) {
|
|
1580
1589
|
switch (node.type) {
|
|
1581
1590
|
case "value": {
|
|
1582
1591
|
return {
|
|
1583
|
-
value: evaluateArg(node.args, context.scope, context.generators)
|
|
1592
|
+
value: evaluateArg(node.args, context.scope, context.generators),
|
|
1593
|
+
lastInsertId: node.lastInsertId
|
|
1584
1594
|
};
|
|
1585
1595
|
}
|
|
1586
1596
|
case "seq": {
|
|
@@ -1590,9 +1600,6 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1590
1600
|
}
|
|
1591
1601
|
return result ?? { value: void 0 };
|
|
1592
1602
|
}
|
|
1593
|
-
case "get": {
|
|
1594
|
-
return { value: context.scope[node.args.name] };
|
|
1595
|
-
}
|
|
1596
1603
|
case "let": {
|
|
1597
1604
|
const nestedScope = Object.create(context.scope);
|
|
1598
1605
|
for (const binding of node.args.bindings) {
|
|
@@ -1601,15 +1608,6 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1601
1608
|
}
|
|
1602
1609
|
return this.interpretNode(node.args.expr, { ...context, scope: nestedScope });
|
|
1603
1610
|
}
|
|
1604
|
-
case "getFirstNonEmpty": {
|
|
1605
|
-
for (const name of node.args.names) {
|
|
1606
|
-
const value = context.scope[name];
|
|
1607
|
-
if (!isEmpty(value)) {
|
|
1608
|
-
return { value };
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
return { value: [] };
|
|
1612
|
-
}
|
|
1613
1611
|
case "concat": {
|
|
1614
1612
|
const parts = await Promise.all(
|
|
1615
1613
|
node.args.map((arg) => this.interpretNode(arg, context).then((res) => res.value))
|
|
@@ -1736,9 +1734,6 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1736
1734
|
return await this.interpretNode(node.args.else, context);
|
|
1737
1735
|
}
|
|
1738
1736
|
}
|
|
1739
|
-
case "unit": {
|
|
1740
|
-
return { value: void 0 };
|
|
1741
|
-
}
|
|
1742
1737
|
case "diff": {
|
|
1743
1738
|
const { value: from } = await this.interpretNode(node.args.from, context);
|
|
1744
1739
|
const { value: to } = await this.interpretNode(node.args.to, context);
|
|
@@ -1768,6 +1763,139 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1768
1763
|
}
|
|
1769
1764
|
return { value: record, lastInsertId };
|
|
1770
1765
|
}
|
|
1766
|
+
default:
|
|
1767
|
+
return this.#interpretPureNode(node, context.scope, context.generators);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
#interpretPureNode(node, scope, generators) {
|
|
1771
|
+
switch (node.type) {
|
|
1772
|
+
case "value": {
|
|
1773
|
+
return { value: evaluateArg(node.args, scope, generators), lastInsertId: node.lastInsertId };
|
|
1774
|
+
}
|
|
1775
|
+
case "seq": {
|
|
1776
|
+
let result;
|
|
1777
|
+
for (const arg of node.args) {
|
|
1778
|
+
result = this.#interpretPureNode(arg, scope, generators);
|
|
1779
|
+
}
|
|
1780
|
+
return result ?? { value: void 0 };
|
|
1781
|
+
}
|
|
1782
|
+
case "get": {
|
|
1783
|
+
return { value: scope[node.args.name] };
|
|
1784
|
+
}
|
|
1785
|
+
case "let": {
|
|
1786
|
+
const nestedScope = Object.create(scope);
|
|
1787
|
+
for (const binding of node.args.bindings) {
|
|
1788
|
+
const { value } = this.#interpretPureNode(binding.expr, nestedScope, generators);
|
|
1789
|
+
nestedScope[binding.name] = value;
|
|
1790
|
+
}
|
|
1791
|
+
return this.#interpretPureNode(node.args.expr, nestedScope, generators);
|
|
1792
|
+
}
|
|
1793
|
+
case "getFirstNonEmpty": {
|
|
1794
|
+
for (const name of node.args.names) {
|
|
1795
|
+
const value = scope[name];
|
|
1796
|
+
if (!isEmpty(value)) {
|
|
1797
|
+
return { value };
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return { value: [] };
|
|
1801
|
+
}
|
|
1802
|
+
case "concat": {
|
|
1803
|
+
const parts = node.args.map((arg) => this.#interpretPureNode(arg, scope, generators).value);
|
|
1804
|
+
return {
|
|
1805
|
+
value: parts.length > 0 ? parts.reduce((acc, part) => acc.concat(asList(part)), []) : []
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
case "sum": {
|
|
1809
|
+
const parts = node.args.map((arg) => this.#interpretPureNode(arg, scope, generators).value);
|
|
1810
|
+
return {
|
|
1811
|
+
value: parts.length > 0 ? parts.reduce((acc, part) => asNumber2(acc) + asNumber2(part)) : 0
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
case "reverse": {
|
|
1815
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
|
|
1816
|
+
return { value: Array.isArray(value) ? value.reverse() : value, lastInsertId };
|
|
1817
|
+
}
|
|
1818
|
+
case "unique": {
|
|
1819
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
|
|
1820
|
+
if (!Array.isArray(value)) {
|
|
1821
|
+
return { value, lastInsertId };
|
|
1822
|
+
}
|
|
1823
|
+
if (value.length > 1) {
|
|
1824
|
+
throw new Error(`Expected zero or one element, got ${value.length}`);
|
|
1825
|
+
}
|
|
1826
|
+
return { value: value[0] ?? null, lastInsertId };
|
|
1827
|
+
}
|
|
1828
|
+
case "required": {
|
|
1829
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
|
|
1830
|
+
if (isEmpty(value)) {
|
|
1831
|
+
throw new Error("Required value is empty");
|
|
1832
|
+
}
|
|
1833
|
+
return { value, lastInsertId };
|
|
1834
|
+
}
|
|
1835
|
+
case "mapField": {
|
|
1836
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args.records, scope, generators);
|
|
1837
|
+
return { value: mapField2(value, node.args.field), lastInsertId };
|
|
1838
|
+
}
|
|
1839
|
+
case "join": {
|
|
1840
|
+
const { value: parent, lastInsertId } = this.#interpretPureNode(node.args.parent, scope, generators);
|
|
1841
|
+
if (parent === null) {
|
|
1842
|
+
return { value: null, lastInsertId };
|
|
1843
|
+
}
|
|
1844
|
+
const children = node.args.children.map((joinExpr) => ({
|
|
1845
|
+
joinExpr,
|
|
1846
|
+
childRecords: this.#interpretPureNode(joinExpr.child, scope, generators).value
|
|
1847
|
+
}));
|
|
1848
|
+
return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
|
|
1849
|
+
}
|
|
1850
|
+
case "dataMap": {
|
|
1851
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
|
|
1852
|
+
return { value: applyDataMap(value, node.args.structure, node.args.enums), lastInsertId };
|
|
1853
|
+
}
|
|
1854
|
+
case "validate": {
|
|
1855
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
|
|
1856
|
+
performValidation(value, node.args.rules, node.args);
|
|
1857
|
+
return { value, lastInsertId };
|
|
1858
|
+
}
|
|
1859
|
+
case "if": {
|
|
1860
|
+
const { value } = this.#interpretPureNode(node.args.value, scope, generators);
|
|
1861
|
+
if (doesSatisfyRule(value, node.args.rule)) {
|
|
1862
|
+
return this.#interpretPureNode(node.args.then, scope, generators);
|
|
1863
|
+
} else {
|
|
1864
|
+
return this.#interpretPureNode(node.args.else, scope, generators);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
case "unit": {
|
|
1868
|
+
return { value: void 0 };
|
|
1869
|
+
}
|
|
1870
|
+
case "diff": {
|
|
1871
|
+
const { value: from } = this.#interpretPureNode(node.args.from, scope, generators);
|
|
1872
|
+
const { value: to } = this.#interpretPureNode(node.args.to, scope, generators);
|
|
1873
|
+
const keyGetter = (item) => item !== null ? getRecordKey(asRecord(item), node.args.fields) : null;
|
|
1874
|
+
const toSet = new Set(asList(to).map(keyGetter));
|
|
1875
|
+
return { value: asList(from).filter((item) => !toSet.has(keyGetter(item))) };
|
|
1876
|
+
}
|
|
1877
|
+
case "process": {
|
|
1878
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
|
|
1879
|
+
const ops = cloneObject(node.args.operations);
|
|
1880
|
+
evaluateProcessingParameters(ops, scope, generators);
|
|
1881
|
+
return { value: processRecords(value, ops), lastInsertId };
|
|
1882
|
+
}
|
|
1883
|
+
case "initializeRecord": {
|
|
1884
|
+
const { lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
|
|
1885
|
+
const record = {};
|
|
1886
|
+
for (const [key, initializer] of Object.entries(node.args.fields)) {
|
|
1887
|
+
record[key] = evalFieldInitializer(initializer, lastInsertId, scope, generators);
|
|
1888
|
+
}
|
|
1889
|
+
return { value: record, lastInsertId };
|
|
1890
|
+
}
|
|
1891
|
+
case "mapRecord": {
|
|
1892
|
+
const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
|
|
1893
|
+
const record = value === null ? {} : asRecord(value);
|
|
1894
|
+
for (const [key, entry] of Object.entries(node.args.fields)) {
|
|
1895
|
+
record[key] = evalFieldOperation(entry, record[key], scope, generators);
|
|
1896
|
+
}
|
|
1897
|
+
return { value: record, lastInsertId };
|
|
1898
|
+
}
|
|
1771
1899
|
default:
|
|
1772
1900
|
assertNever(node, `Unexpected node type: ${node.type}`);
|
|
1773
1901
|
}
|
|
@@ -1945,6 +2073,112 @@ function evalFieldOperation(op, value, scope, generators) {
|
|
|
1945
2073
|
assertNever(op, `Unexpected field operation type: ${op["type"]}`);
|
|
1946
2074
|
}
|
|
1947
2075
|
}
|
|
2076
|
+
function purifyQueryPlan(node, evalNode) {
|
|
2077
|
+
const impureNode = findUniqueUnconditionalImpureNode(node);
|
|
2078
|
+
if (!impureNode) {
|
|
2079
|
+
return void 0;
|
|
2080
|
+
}
|
|
2081
|
+
return evalNode(impureNode).then((result) => {
|
|
2082
|
+
const evaluated = {
|
|
2083
|
+
type: "value",
|
|
2084
|
+
args: result.value,
|
|
2085
|
+
lastInsertId: result.lastInsertId
|
|
2086
|
+
};
|
|
2087
|
+
const purified = replaceImpureNode(node, impureNode, evaluated);
|
|
2088
|
+
if (!purified) {
|
|
2089
|
+
throw new Error("Could not substitute the evaluated impure node into the query plan");
|
|
2090
|
+
}
|
|
2091
|
+
return purified;
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
function replaceImpureNode(node, target, replacement) {
|
|
2095
|
+
if (node === target) {
|
|
2096
|
+
return replacement;
|
|
2097
|
+
}
|
|
2098
|
+
switch (node.type) {
|
|
2099
|
+
case "seq":
|
|
2100
|
+
case "sum":
|
|
2101
|
+
case "concat": {
|
|
2102
|
+
for (let i = 0; i < node.args.length; i++) {
|
|
2103
|
+
const child = replaceImpureNode(node.args[i], target, replacement);
|
|
2104
|
+
if (child) {
|
|
2105
|
+
return { ...node, args: node.args.map((arg, j) => j === i ? child : arg) };
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
return void 0;
|
|
2109
|
+
}
|
|
2110
|
+
case "dataMap":
|
|
2111
|
+
case "validate":
|
|
2112
|
+
case "initializeRecord":
|
|
2113
|
+
case "mapRecord":
|
|
2114
|
+
case "process": {
|
|
2115
|
+
const expr = replaceImpureNode(node.args.expr, target, replacement);
|
|
2116
|
+
return expr && { ...node, args: { ...node.args, expr } };
|
|
2117
|
+
}
|
|
2118
|
+
case "mapField": {
|
|
2119
|
+
const records = replaceImpureNode(node.args.records, target, replacement);
|
|
2120
|
+
return records && { ...node, args: { ...node.args, records } };
|
|
2121
|
+
}
|
|
2122
|
+
case "reverse":
|
|
2123
|
+
case "unique":
|
|
2124
|
+
case "required": {
|
|
2125
|
+
const args = replaceImpureNode(node.args, target, replacement);
|
|
2126
|
+
return args && { ...node, args };
|
|
2127
|
+
}
|
|
2128
|
+
default:
|
|
2129
|
+
return void 0;
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
function findUniqueUnconditionalImpureNode(node) {
|
|
2133
|
+
switch (node.type) {
|
|
2134
|
+
case "query":
|
|
2135
|
+
case "execute":
|
|
2136
|
+
return node;
|
|
2137
|
+
case "seq":
|
|
2138
|
+
case "sum":
|
|
2139
|
+
case "concat": {
|
|
2140
|
+
let found = void 0;
|
|
2141
|
+
for (const child of node.args) {
|
|
2142
|
+
const childFound = findUniqueUnconditionalImpureNode(child);
|
|
2143
|
+
if (childFound === null) {
|
|
2144
|
+
return null;
|
|
2145
|
+
}
|
|
2146
|
+
if (childFound) {
|
|
2147
|
+
if (found) {
|
|
2148
|
+
return null;
|
|
2149
|
+
}
|
|
2150
|
+
found = childFound;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
return found;
|
|
2154
|
+
}
|
|
2155
|
+
case "dataMap":
|
|
2156
|
+
case "validate":
|
|
2157
|
+
case "initializeRecord":
|
|
2158
|
+
case "mapRecord":
|
|
2159
|
+
case "process":
|
|
2160
|
+
return findUniqueUnconditionalImpureNode(node.args.expr);
|
|
2161
|
+
case "mapField":
|
|
2162
|
+
return findUniqueUnconditionalImpureNode(node.args.records);
|
|
2163
|
+
case "reverse":
|
|
2164
|
+
case "unique":
|
|
2165
|
+
case "required":
|
|
2166
|
+
return findUniqueUnconditionalImpureNode(node.args);
|
|
2167
|
+
case "let":
|
|
2168
|
+
case "join":
|
|
2169
|
+
case "diff":
|
|
2170
|
+
case "if":
|
|
2171
|
+
case "transaction":
|
|
2172
|
+
return null;
|
|
2173
|
+
case "value":
|
|
2174
|
+
case "get":
|
|
2175
|
+
case "getFirstNonEmpty":
|
|
2176
|
+
case "unit":
|
|
2177
|
+
return void 0;
|
|
2178
|
+
default:
|
|
2179
|
+
assertNever(node, `Unexpected node type: ${node.type}`);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
1948
2182
|
function applyComments(query, sqlCommenter) {
|
|
1949
2183
|
if (!sqlCommenter || sqlCommenter.plugins.length === 0) {
|
|
1950
2184
|
return query;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ConnectionInfo, SqlQueryable, SqlResultSet } from '@prisma/driver-adapter-utils';
|
|
2
2
|
import type { SqlCommenterPlugin, SqlCommenterQueryInfo } from '@prisma/sqlcommenter';
|
|
3
3
|
import { QueryEvent } from '../events';
|
|
4
|
-
import { QueryPlanNode } from '../query-plan';
|
|
4
|
+
import { ImpureQueryPlanNode, PureQueryPlanNode, QueryPlanNode } from '../query-plan';
|
|
5
5
|
import { type SchemaProvider } from '../schema';
|
|
6
6
|
import { type TracingHelper } from '../tracing';
|
|
7
7
|
import { type TransactionManager } from '../transaction-manager/transaction-manager';
|
|
@@ -43,3 +43,22 @@ export declare class QueryInterpreter {
|
|
|
43
43
|
run(queryPlan: DeepReadonly<QueryPlanNode>, options: QueryRuntimeOptions): Promise<unknown>;
|
|
44
44
|
private interpretNode;
|
|
45
45
|
}
|
|
46
|
+
type IntermediateValue = {
|
|
47
|
+
value: Value;
|
|
48
|
+
lastInsertId?: string;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Attempts to convert a query plan into a pure one by finding the single impure node
|
|
52
|
+
* that is unconditionally evaluated exactly once, evaluating it eagerly via `evalNode`,
|
|
53
|
+
* and substituting its result into the plan as a constant. The remaining plan can then
|
|
54
|
+
* be interpreted synchronously.
|
|
55
|
+
*
|
|
56
|
+
* Returns `undefined` if the plan cannot be purified this way (it contains no impure
|
|
57
|
+
* nodes, more than one of them, or unsupported constructs).
|
|
58
|
+
*
|
|
59
|
+
* Query plans are cached and shared between requests, so the input plan is never
|
|
60
|
+
* modified; the returned plan shares all unaffected subtrees with the input and only
|
|
61
|
+
* copies the nodes on the path to the substituted one.
|
|
62
|
+
*/
|
|
63
|
+
export declare function purifyQueryPlan(node: DeepReadonly<QueryPlanNode>, evalNode: (node: DeepReadonly<ImpureQueryPlanNode>) => Promise<IntermediateValue>): Promise<DeepReadonly<PureQueryPlanNode>> | undefined;
|
|
64
|
+
export {};
|