@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.js CHANGED
@@ -1623,17 +1623,27 @@ var QueryInterpreter = class _QueryInterpreter {
1623
1623
  });
1624
1624
  }
1625
1625
  async run(queryPlan, options) {
1626
- const { value } = await this.interpretNode(queryPlan, {
1627
- ...options,
1628
- generators: this.#generators.snapshot()
1629
- }).catch((e) => rethrowAsUserFacing(e));
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 { value } = await this.interpretNode(queryPlan, {
1574
- ...options,
1575
- generators: this.#generators.snapshot()
1576
- }).catch((e) => rethrowAsUserFacing(e));
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 {};