@prisma/client-engine-runtime 7.10.0-dev.4 → 7.10.0-dev.41

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
@@ -56,6 +56,12 @@ var import_client_runtime_utils2 = require("@prisma/client-runtime-utils");
56
56
 
57
57
  // src/utils.ts
58
58
  var import_client_runtime_utils = require("@prisma/client-runtime-utils");
59
+ function isUint8Array(value) {
60
+ return ArrayBuffer.isView(value) && Object.prototype.toString.call(value) === "[object Uint8Array]";
61
+ }
62
+ function isDate(value) {
63
+ return Object.prototype.toString.call(value) === "[object Date]";
64
+ }
59
65
  function assertNever(_, message) {
60
66
  throw new Error(message);
61
67
  }
@@ -74,11 +80,11 @@ function doKeysMatch(lhs, rhs) {
74
80
  const lhsDecimal = asDecimal(lhs[key]);
75
81
  const rhsDecimal = asDecimal(rhs[key]);
76
82
  return lhsDecimal && rhsDecimal && lhsDecimal.equals(rhsDecimal);
77
- } else if (lhs[key] instanceof Uint8Array || rhs[key] instanceof Uint8Array) {
83
+ } else if (isUint8Array(lhs[key]) || isUint8Array(rhs[key])) {
78
84
  const lhsBuffer = asBuffer(lhs[key]);
79
85
  const rhsBuffer = asBuffer(rhs[key]);
80
86
  return lhsBuffer && rhsBuffer && lhsBuffer.equals(rhsBuffer);
81
- } else if (lhs[key] instanceof Date || rhs[key] instanceof Date) {
87
+ } else if (isDate(lhs[key]) || isDate(rhs[key])) {
82
88
  return asDate(lhs[key])?.getTime() === asDate(rhs[key])?.getTime();
83
89
  } else if (typeof lhs[key] === "bigint" || typeof rhs[key] === "bigint") {
84
90
  return asBigInt(lhs[key]) === asBigInt(rhs[key]);
@@ -100,7 +106,7 @@ function asDecimal(value) {
100
106
  function asBuffer(value) {
101
107
  if (Buffer.isBuffer(value)) {
102
108
  return value;
103
- } else if (value instanceof Uint8Array) {
109
+ } else if (isUint8Array(value)) {
104
110
  return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
105
111
  } else if (typeof value === "string") {
106
112
  return Buffer.from(value, "base64");
@@ -109,7 +115,7 @@ function asBuffer(value) {
109
115
  }
110
116
  }
111
117
  function asDate(value) {
112
- if (value instanceof Date) {
118
+ if (isDate(value)) {
113
119
  return value;
114
120
  } else if (typeof value === "string" || typeof value === "number") {
115
121
  return new Date(value);
@@ -145,6 +151,16 @@ function safeJsonStringify(obj) {
145
151
  return val;
146
152
  });
147
153
  }
154
+ var MAX_PUSH_SPREAD_ARGS = 8192;
155
+ function appendToArray(target, source) {
156
+ if (source.length <= MAX_PUSH_SPREAD_ARGS) {
157
+ target.push(...source);
158
+ return;
159
+ }
160
+ for (let i = 0; i < source.length; i += MAX_PUSH_SPREAD_ARGS) {
161
+ target.push(...source.slice(i, i + MAX_PUSH_SPREAD_ARGS));
162
+ }
163
+ }
148
164
 
149
165
  // src/json-protocol.ts
150
166
  function normalizeJsonProtocolValues(result) {
@@ -226,10 +242,8 @@ function deserializeTaggedValue({ $type, value }) {
226
242
  switch ($type) {
227
243
  case "BigInt":
228
244
  return BigInt(value);
229
- case "Bytes": {
230
- const { buffer, byteOffset, byteLength } = Buffer.from(value, "base64");
231
- return new Uint8Array(buffer, byteOffset, byteLength);
232
- }
245
+ case "Bytes":
246
+ return new Uint8Array(Buffer.from(value, "base64"));
233
247
  case "DateTime":
234
248
  return new Date(value);
235
249
  case "Decimal":
@@ -343,6 +357,7 @@ function getErrorCode(err) {
343
357
  case "UniqueConstraintViolation":
344
358
  return "P2002";
345
359
  case "ForeignKeyConstraintViolation":
360
+ case "RestrictViolation":
346
361
  return "P2003";
347
362
  case "InvalidInputValue":
348
363
  return "P2007";
@@ -415,6 +430,7 @@ function renderErrorMessage(err) {
415
430
  case "UniqueConstraintViolation":
416
431
  return `Unique constraint failed on the ${renderConstraint(err.cause.constraint)}`;
417
432
  case "ForeignKeyConstraintViolation":
433
+ case "RestrictViolation":
418
434
  return `Foreign key constraint violated on the ${renderConstraint(err.cause.constraint)}`;
419
435
  case "UnsupportedNativeDataType":
420
436
  return `Failed to deserialize column of type '${err.cause.type}'. If you're using $queryRaw and this column is explicitly marked as \`Unsupported\` in your Prisma schema, try casting this column to any supported Prisma type such as \`String\`.`;
@@ -691,7 +707,7 @@ function mapValue(value, columnName, scalarType, enums) {
691
707
  throw new DataMapperError(`Expected a boolean in column '${columnName}', got ${typeof value}: ${value}`);
692
708
  }
693
709
  }
694
- if (Array.isArray(value) || value instanceof Uint8Array) {
710
+ if (Array.isArray(value) || isUint8Array(value)) {
695
711
  for (const byte of value) {
696
712
  if (byte !== 0) return true;
697
713
  }
@@ -708,7 +724,7 @@ function mapValue(value, columnName, scalarType, enums) {
708
724
  if (typeof value === "string") {
709
725
  return { $type: "DateTime", value: normalizeDateTime(value) };
710
726
  }
711
- if (typeof value === "number" || value instanceof Date) {
727
+ if (typeof value === "number" || isDate(value)) {
712
728
  return { $type: "DateTime", value };
713
729
  }
714
730
  throw new DataMapperError(`Expected a date in column '${columnName}', got ${typeof value}: ${value}`);
@@ -739,7 +755,7 @@ function mapValue(value, columnName, scalarType, enums) {
739
755
  if (Array.isArray(value)) {
740
756
  return { $type: "Bytes", value: Buffer.from(value).toString("base64") };
741
757
  }
742
- if (value instanceof Uint8Array) {
758
+ if (isUint8Array(value)) {
743
759
  return { $type: "Bytes", value: Buffer.from(value).toString("base64") };
744
760
  }
745
761
  throw new DataMapperError(`Expected a byte array in column '${columnName}', got ${typeof value}: ${value}`);
@@ -787,6 +803,7 @@ function normalizeDateTime(dt) {
787
803
  }
788
804
 
789
805
  // src/interpreter/query-interpreter.ts
806
+ var import_debug = require("@prisma/debug");
790
807
  var import_klona2 = require("klona");
791
808
 
792
809
  // src/sql-commenter.ts
@@ -1148,8 +1165,9 @@ function renderTemplateSql(fragments, placeholderFormat, params, argTypes) {
1148
1165
  if (fragment.type === "stringChunk") {
1149
1166
  continue;
1150
1167
  }
1151
- const length = flattenedParams.length;
1152
- const added = flattenedParams.push(...flattenedFragmentParams(fragment)) - length;
1168
+ const fragmentParams = Array.from(flattenedFragmentParams(fragment));
1169
+ const added = fragmentParams.length;
1170
+ appendToArray(flattenedParams, fragmentParams);
1153
1171
  if (fragment.argType.arity === "tuple") {
1154
1172
  if (added % fragment.argType.elements.length !== 0) {
1155
1173
  throw new Error(
@@ -1578,6 +1596,7 @@ function getErrorCode2(error) {
1578
1596
  }
1579
1597
 
1580
1598
  // src/interpreter/query-interpreter.ts
1599
+ var debug = (0, import_debug.Debug)("prisma:client:queryInterpreter");
1581
1600
  var QueryInterpreter = class _QueryInterpreter {
1582
1601
  #onQuery;
1583
1602
  #generators = new GeneratorRegistry();
@@ -1612,17 +1631,27 @@ var QueryInterpreter = class _QueryInterpreter {
1612
1631
  });
1613
1632
  }
1614
1633
  async run(queryPlan, options) {
1615
- const { value } = await this.interpretNode(queryPlan, {
1616
- ...options,
1617
- generators: this.#generators.snapshot()
1618
- }).catch((e) => rethrowAsUserFacing(e));
1634
+ const generators = this.#generators.snapshot();
1635
+ const context = { ...options, generators };
1636
+ const purified = purifyQueryPlan(queryPlan, (node) => this.interpretNode(node, context))?.catch(
1637
+ (e) => rethrowAsUserFacing(e)
1638
+ );
1639
+ if (purified) {
1640
+ try {
1641
+ return this.#interpretPureNode(await purified, context.scope, generators).value;
1642
+ } catch (e) {
1643
+ rethrowAsUserFacing(e);
1644
+ }
1645
+ }
1646
+ const { value } = await this.interpretNode(queryPlan, context).catch((e) => rethrowAsUserFacing(e));
1619
1647
  return value;
1620
1648
  }
1621
1649
  async interpretNode(node, context) {
1622
1650
  switch (node.type) {
1623
1651
  case "value": {
1624
1652
  return {
1625
- value: evaluateArg(node.args, context.scope, context.generators)
1653
+ value: evaluateArg(node.args, context.scope, context.generators),
1654
+ lastInsertId: node.lastInsertId
1626
1655
  };
1627
1656
  }
1628
1657
  case "seq": {
@@ -1632,9 +1661,6 @@ var QueryInterpreter = class _QueryInterpreter {
1632
1661
  }
1633
1662
  return result ?? { value: void 0 };
1634
1663
  }
1635
- case "get": {
1636
- return { value: context.scope[node.args.name] };
1637
- }
1638
1664
  case "let": {
1639
1665
  const nestedScope = Object.create(context.scope);
1640
1666
  for (const binding of node.args.bindings) {
@@ -1643,15 +1669,6 @@ var QueryInterpreter = class _QueryInterpreter {
1643
1669
  }
1644
1670
  return this.interpretNode(node.args.expr, { ...context, scope: nestedScope });
1645
1671
  }
1646
- case "getFirstNonEmpty": {
1647
- for (const name of node.args.names) {
1648
- const value = context.scope[name];
1649
- if (!isEmpty(value)) {
1650
- return { value };
1651
- }
1652
- }
1653
- return { value: [] };
1654
- }
1655
1672
  case "concat": {
1656
1673
  const parts = await Promise.all(
1657
1674
  node.args.map((arg) => this.interpretNode(arg, context).then((res) => res.value))
@@ -1670,42 +1687,46 @@ var QueryInterpreter = class _QueryInterpreter {
1670
1687
  }
1671
1688
  case "execute": {
1672
1689
  const queries = renderQuery(node.args, context.scope, context.generators, this.#maxChunkSize());
1673
- let sum = 0;
1674
- for (const query of queries) {
1675
- const commentedQuery = applyComments(query, context.sqlCommenter);
1676
- sum += await this.#withQuerySpanAndEvent(
1677
- commentedQuery,
1678
- context.queryable,
1679
- () => context.queryable.executeRaw(cloneObject(commentedQuery)).catch(
1680
- (err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
1681
- )
1682
- );
1683
- }
1684
- return { value: sum };
1690
+ return this.#withChunkTransaction(queries.length, context, async (context2) => {
1691
+ let sum = 0;
1692
+ for (const query of queries) {
1693
+ const commentedQuery = applyComments(query, context2.sqlCommenter);
1694
+ sum += await this.#withQuerySpanAndEvent(
1695
+ commentedQuery,
1696
+ context2.queryable,
1697
+ () => context2.queryable.executeRaw(cloneObject(commentedQuery)).catch(
1698
+ (err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
1699
+ )
1700
+ );
1701
+ }
1702
+ return { value: sum };
1703
+ });
1685
1704
  }
1686
1705
  case "query": {
1687
1706
  const queries = renderQuery(node.args, context.scope, context.generators, this.#maxChunkSize());
1688
- let results;
1689
- for (const query of queries) {
1690
- const commentedQuery = applyComments(query, context.sqlCommenter);
1691
- const result = await this.#withQuerySpanAndEvent(
1692
- commentedQuery,
1693
- context.queryable,
1694
- () => context.queryable.queryRaw(cloneObject(commentedQuery)).catch(
1695
- (err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
1696
- )
1697
- );
1698
- if (results === void 0) {
1699
- results = result;
1700
- } else {
1701
- results.rows.push(...result.rows);
1702
- results.lastInsertId = result.lastInsertId;
1707
+ return this.#withChunkTransaction(queries.length, context, async (context2) => {
1708
+ let results;
1709
+ for (const query of queries) {
1710
+ const commentedQuery = applyComments(query, context2.sqlCommenter);
1711
+ const result = await this.#withQuerySpanAndEvent(
1712
+ commentedQuery,
1713
+ context2.queryable,
1714
+ () => context2.queryable.queryRaw(cloneObject(commentedQuery)).catch(
1715
+ (err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
1716
+ )
1717
+ );
1718
+ if (results === void 0) {
1719
+ results = result;
1720
+ } else {
1721
+ appendToArray(results.rows, result.rows);
1722
+ results.lastInsertId = result.lastInsertId;
1723
+ }
1703
1724
  }
1704
- }
1705
- return {
1706
- value: node.args.type === "rawSql" ? this.#rawSerializer(results) : this.#serializer(results),
1707
- lastInsertId: results?.lastInsertId
1708
- };
1725
+ return {
1726
+ value: node.args.type === "rawSql" ? this.#rawSerializer(results) : this.#serializer(results),
1727
+ lastInsertId: results?.lastInsertId
1728
+ };
1729
+ });
1709
1730
  }
1710
1731
  case "reverse": {
1711
1732
  const { value, lastInsertId } = await this.interpretNode(node.args, context);
@@ -1746,20 +1767,7 @@ var QueryInterpreter = class _QueryInterpreter {
1746
1767
  return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
1747
1768
  }
1748
1769
  case "transaction": {
1749
- if (!context.transactionManager.enabled) {
1750
- return this.interpretNode(node.args, context);
1751
- }
1752
- const transactionManager = context.transactionManager.manager;
1753
- const transactionInfo = await transactionManager.startInternalTransaction();
1754
- const transaction = await transactionManager.getTransaction(transactionInfo, "query");
1755
- try {
1756
- const value = await this.interpretNode(node.args, { ...context, queryable: transaction });
1757
- await transactionManager.commitTransaction(transactionInfo.id);
1758
- return value;
1759
- } catch (e) {
1760
- await transactionManager.rollbackTransaction(transactionInfo.id);
1761
- throw e;
1762
- }
1770
+ return this.#withInternalTransaction(context, (context2) => this.interpretNode(node.args, context2));
1763
1771
  }
1764
1772
  case "dataMap": {
1765
1773
  const { value, lastInsertId } = await this.interpretNode(node.args.expr, context);
@@ -1778,9 +1786,6 @@ var QueryInterpreter = class _QueryInterpreter {
1778
1786
  return await this.interpretNode(node.args.else, context);
1779
1787
  }
1780
1788
  }
1781
- case "unit": {
1782
- return { value: void 0 };
1783
- }
1784
1789
  case "diff": {
1785
1790
  const { value: from } = await this.interpretNode(node.args.from, context);
1786
1791
  const { value: to } = await this.interpretNode(node.args.to, context);
@@ -1810,10 +1815,185 @@ var QueryInterpreter = class _QueryInterpreter {
1810
1815
  }
1811
1816
  return { value: record, lastInsertId };
1812
1817
  }
1818
+ default:
1819
+ return this.#interpretPureNode(node, context.scope, context.generators);
1820
+ }
1821
+ }
1822
+ #interpretPureNode(node, scope, generators) {
1823
+ switch (node.type) {
1824
+ case "value": {
1825
+ return { value: evaluateArg(node.args, scope, generators), lastInsertId: node.lastInsertId };
1826
+ }
1827
+ case "seq": {
1828
+ let result;
1829
+ for (const arg of node.args) {
1830
+ result = this.#interpretPureNode(arg, scope, generators);
1831
+ }
1832
+ return result ?? { value: void 0 };
1833
+ }
1834
+ case "get": {
1835
+ return { value: scope[node.args.name] };
1836
+ }
1837
+ case "let": {
1838
+ const nestedScope = Object.create(scope);
1839
+ for (const binding of node.args.bindings) {
1840
+ const { value } = this.#interpretPureNode(binding.expr, nestedScope, generators);
1841
+ nestedScope[binding.name] = value;
1842
+ }
1843
+ return this.#interpretPureNode(node.args.expr, nestedScope, generators);
1844
+ }
1845
+ case "getFirstNonEmpty": {
1846
+ for (const name of node.args.names) {
1847
+ const value = scope[name];
1848
+ if (!isEmpty(value)) {
1849
+ return { value };
1850
+ }
1851
+ }
1852
+ return { value: [] };
1853
+ }
1854
+ case "concat": {
1855
+ const parts = node.args.map((arg) => this.#interpretPureNode(arg, scope, generators).value);
1856
+ return {
1857
+ value: parts.length > 0 ? parts.reduce((acc, part) => acc.concat(asList(part)), []) : []
1858
+ };
1859
+ }
1860
+ case "sum": {
1861
+ const parts = node.args.map((arg) => this.#interpretPureNode(arg, scope, generators).value);
1862
+ return {
1863
+ value: parts.length > 0 ? parts.reduce((acc, part) => asNumber2(acc) + asNumber2(part)) : 0
1864
+ };
1865
+ }
1866
+ case "reverse": {
1867
+ const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
1868
+ return { value: Array.isArray(value) ? value.reverse() : value, lastInsertId };
1869
+ }
1870
+ case "unique": {
1871
+ const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
1872
+ if (!Array.isArray(value)) {
1873
+ return { value, lastInsertId };
1874
+ }
1875
+ if (value.length > 1) {
1876
+ throw new Error(`Expected zero or one element, got ${value.length}`);
1877
+ }
1878
+ return { value: value[0] ?? null, lastInsertId };
1879
+ }
1880
+ case "required": {
1881
+ const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
1882
+ if (isEmpty(value)) {
1883
+ throw new Error("Required value is empty");
1884
+ }
1885
+ return { value, lastInsertId };
1886
+ }
1887
+ case "mapField": {
1888
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.records, scope, generators);
1889
+ return { value: mapField2(value, node.args.field), lastInsertId };
1890
+ }
1891
+ case "join": {
1892
+ const { value: parent, lastInsertId } = this.#interpretPureNode(node.args.parent, scope, generators);
1893
+ if (parent === null) {
1894
+ return { value: null, lastInsertId };
1895
+ }
1896
+ const children = node.args.children.map((joinExpr) => ({
1897
+ joinExpr,
1898
+ childRecords: this.#interpretPureNode(joinExpr.child, scope, generators).value
1899
+ }));
1900
+ return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
1901
+ }
1902
+ case "dataMap": {
1903
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1904
+ return { value: applyDataMap(value, node.args.structure, node.args.enums), lastInsertId };
1905
+ }
1906
+ case "validate": {
1907
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1908
+ performValidation(value, node.args.rules, node.args);
1909
+ return { value, lastInsertId };
1910
+ }
1911
+ case "if": {
1912
+ const { value } = this.#interpretPureNode(node.args.value, scope, generators);
1913
+ if (doesSatisfyRule(value, node.args.rule)) {
1914
+ return this.#interpretPureNode(node.args.then, scope, generators);
1915
+ } else {
1916
+ return this.#interpretPureNode(node.args.else, scope, generators);
1917
+ }
1918
+ }
1919
+ case "unit": {
1920
+ return { value: void 0 };
1921
+ }
1922
+ case "diff": {
1923
+ const { value: from } = this.#interpretPureNode(node.args.from, scope, generators);
1924
+ const { value: to } = this.#interpretPureNode(node.args.to, scope, generators);
1925
+ const keyGetter = (item) => item !== null ? getRecordKey(asRecord(item), node.args.fields) : null;
1926
+ const toSet = new Set(asList(to).map(keyGetter));
1927
+ return { value: asList(from).filter((item) => !toSet.has(keyGetter(item))) };
1928
+ }
1929
+ case "process": {
1930
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1931
+ const ops = cloneObject(node.args.operations);
1932
+ evaluateProcessingParameters(ops, scope, generators);
1933
+ return { value: processRecords(value, ops), lastInsertId };
1934
+ }
1935
+ case "initializeRecord": {
1936
+ const { lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1937
+ const record = {};
1938
+ for (const [key, initializer] of Object.entries(node.args.fields)) {
1939
+ record[key] = evalFieldInitializer(initializer, lastInsertId, scope, generators);
1940
+ }
1941
+ return { value: record, lastInsertId };
1942
+ }
1943
+ case "mapRecord": {
1944
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1945
+ const record = value === null ? {} : asRecord(value);
1946
+ for (const [key, entry] of Object.entries(node.args.fields)) {
1947
+ record[key] = evalFieldOperation(entry, record[key], scope, generators);
1948
+ }
1949
+ return { value: record, lastInsertId };
1950
+ }
1813
1951
  default:
1814
1952
  assertNever(node, `Unexpected node type: ${node.type}`);
1815
1953
  }
1816
1954
  }
1955
+ /**
1956
+ * Runs the statements of a `query` or `execute` node via `fn`, wrapping them in a
1957
+ * transaction when a chunkable statement was split into multiple queries at render time,
1958
+ * so that a partially applied write cannot be observed or left behind if a later chunk
1959
+ * fails. A single statement is atomic on its own, so it runs on the current context.
1960
+ */
1961
+ #withChunkTransaction(statementCount, context, fn) {
1962
+ if (statementCount <= 1) {
1963
+ return fn(context);
1964
+ }
1965
+ return this.#withInternalTransaction(context, fn);
1966
+ }
1967
+ /**
1968
+ * Runs `fn` with a context whose queryable is guaranteed to be a transaction, starting a
1969
+ * new internal transaction and committing or rolling it back around the call.
1970
+ *
1971
+ * A disabled transaction manager means the queryable already is a transaction: executors
1972
+ * pass `{ enabled: false }` when the plan runs inside an interactive transaction, and the
1973
+ * context handed to `fn` carries it for the duration of an internal transaction. In that
1974
+ * case `fn` runs on the current context, since the statements it issues are already
1975
+ * covered by the surrounding transaction.
1976
+ */
1977
+ async #withInternalTransaction(context, fn) {
1978
+ if (!context.transactionManager.enabled) {
1979
+ return fn(context);
1980
+ }
1981
+ const transactionManager = context.transactionManager.manager;
1982
+ const transactionInfo = await transactionManager.startInternalTransaction();
1983
+ const transaction = await transactionManager.getTransaction(transactionInfo, "query");
1984
+ try {
1985
+ const result = await fn({ ...context, queryable: transaction, transactionManager: { enabled: false } });
1986
+ await transactionManager.commitTransaction(transactionInfo.id);
1987
+ return result;
1988
+ } catch (e) {
1989
+ try {
1990
+ await transactionManager.rollbackTransaction(transactionInfo.id);
1991
+ } catch (rollbackError) {
1992
+ debug("failed to roll back an internal transaction", rollbackError);
1993
+ }
1994
+ throw e;
1995
+ }
1996
+ }
1817
1997
  #maxChunkSize() {
1818
1998
  if (this.#connectionInfo?.maxBindValues !== void 0) {
1819
1999
  return this.#connectionInfo.maxBindValues;
@@ -1987,6 +2167,112 @@ function evalFieldOperation(op, value, scope, generators) {
1987
2167
  assertNever(op, `Unexpected field operation type: ${op["type"]}`);
1988
2168
  }
1989
2169
  }
2170
+ function purifyQueryPlan(node, evalNode) {
2171
+ const impureNode = findUniqueUnconditionalImpureNode(node);
2172
+ if (!impureNode) {
2173
+ return void 0;
2174
+ }
2175
+ return evalNode(impureNode).then((result) => {
2176
+ const evaluated = {
2177
+ type: "value",
2178
+ args: result.value,
2179
+ lastInsertId: result.lastInsertId
2180
+ };
2181
+ const purified = replaceImpureNode(node, impureNode, evaluated);
2182
+ if (!purified) {
2183
+ throw new Error("Could not substitute the evaluated impure node into the query plan");
2184
+ }
2185
+ return purified;
2186
+ });
2187
+ }
2188
+ function replaceImpureNode(node, target, replacement) {
2189
+ if (node === target) {
2190
+ return replacement;
2191
+ }
2192
+ switch (node.type) {
2193
+ case "seq":
2194
+ case "sum":
2195
+ case "concat": {
2196
+ for (let i = 0; i < node.args.length; i++) {
2197
+ const child = replaceImpureNode(node.args[i], target, replacement);
2198
+ if (child) {
2199
+ return { ...node, args: node.args.map((arg, j) => j === i ? child : arg) };
2200
+ }
2201
+ }
2202
+ return void 0;
2203
+ }
2204
+ case "dataMap":
2205
+ case "validate":
2206
+ case "initializeRecord":
2207
+ case "mapRecord":
2208
+ case "process": {
2209
+ const expr = replaceImpureNode(node.args.expr, target, replacement);
2210
+ return expr && { ...node, args: { ...node.args, expr } };
2211
+ }
2212
+ case "mapField": {
2213
+ const records = replaceImpureNode(node.args.records, target, replacement);
2214
+ return records && { ...node, args: { ...node.args, records } };
2215
+ }
2216
+ case "reverse":
2217
+ case "unique":
2218
+ case "required": {
2219
+ const args = replaceImpureNode(node.args, target, replacement);
2220
+ return args && { ...node, args };
2221
+ }
2222
+ default:
2223
+ return void 0;
2224
+ }
2225
+ }
2226
+ function findUniqueUnconditionalImpureNode(node) {
2227
+ switch (node.type) {
2228
+ case "query":
2229
+ case "execute":
2230
+ return node;
2231
+ case "seq":
2232
+ case "sum":
2233
+ case "concat": {
2234
+ let found = void 0;
2235
+ for (const child of node.args) {
2236
+ const childFound = findUniqueUnconditionalImpureNode(child);
2237
+ if (childFound === null) {
2238
+ return null;
2239
+ }
2240
+ if (childFound) {
2241
+ if (found) {
2242
+ return null;
2243
+ }
2244
+ found = childFound;
2245
+ }
2246
+ }
2247
+ return found;
2248
+ }
2249
+ case "dataMap":
2250
+ case "validate":
2251
+ case "initializeRecord":
2252
+ case "mapRecord":
2253
+ case "process":
2254
+ return findUniqueUnconditionalImpureNode(node.args.expr);
2255
+ case "mapField":
2256
+ return findUniqueUnconditionalImpureNode(node.args.records);
2257
+ case "reverse":
2258
+ case "unique":
2259
+ case "required":
2260
+ return findUniqueUnconditionalImpureNode(node.args);
2261
+ case "let":
2262
+ case "join":
2263
+ case "diff":
2264
+ case "if":
2265
+ case "transaction":
2266
+ return null;
2267
+ case "value":
2268
+ case "get":
2269
+ case "getFirstNonEmpty":
2270
+ case "unit":
2271
+ return void 0;
2272
+ default:
2273
+ assertNever(node, `Unexpected node type: ${node.type}`);
2274
+ }
2275
+ }
1990
2276
  function applyComments(query, sqlCommenter) {
1991
2277
  if (!sqlCommenter || sqlCommenter.plugins.length === 0) {
1992
2278
  return query;
@@ -2485,7 +2771,7 @@ function normalizeValue(type, value) {
2485
2771
  }
2486
2772
 
2487
2773
  // src/transaction-manager/transaction-manager.ts
2488
- var import_debug = require("@prisma/debug");
2774
+ var import_debug2 = require("@prisma/debug");
2489
2775
 
2490
2776
  // src/crypto.ts
2491
2777
  async function getCrypto() {
@@ -2553,7 +2839,15 @@ var InvalidTransactionIsolationLevelError = class extends TransactionManagerErro
2553
2839
 
2554
2840
  // src/transaction-manager/transaction-manager.ts
2555
2841
  var MAX_CLOSED_TRANSACTIONS = 100;
2556
- var debug = (0, import_debug.Debug)("prisma:client:transactionManager");
2842
+ var CANCEL_ROLLBACK_GRACE_MS = 2e3;
2843
+ function trackStartingTransaction() {
2844
+ let markSettled;
2845
+ const settled = new Promise((resolve) => {
2846
+ markSettled = resolve;
2847
+ });
2848
+ return { abortController: new AbortController(), settled, markSettled };
2849
+ }
2850
+ var debug2 = (0, import_debug2.Debug)("prisma:client:transactionManager");
2557
2851
  var COMMIT_QUERY = () => ({ sql: "COMMIT", args: [], argTypes: [] });
2558
2852
  var ROLLBACK_QUERY = () => ({ sql: "ROLLBACK", args: [], argTypes: [] });
2559
2853
  var PHANTOM_COMMIT_QUERY = () => ({
@@ -2572,6 +2866,9 @@ var TransactionManager = class {
2572
2866
  // List of last closed transactions. Max MAX_CLOSED_TRANSACTIONS entries.
2573
2867
  // Used to provide better error messages than a generic "transaction not found".
2574
2868
  closedTransactions = [];
2869
+ // Transactions that are still being started. Tracked separately so that
2870
+ // `cancelAllTransactions` can reach them: they are not in `transactions` yet.
2871
+ #startingTransactions = /* @__PURE__ */ new Set();
2575
2872
  driverAdapter;
2576
2873
  transactionOptions;
2577
2874
  tracingHelper;
@@ -2635,56 +2932,91 @@ var TransactionManager = class {
2635
2932
  return { id: existing.id };
2636
2933
  });
2637
2934
  }
2638
- const transaction = {
2639
- id: await randomUUID(),
2640
- status: "waiting",
2641
- timer: void 0,
2642
- timeout: options.timeout,
2643
- startedAt: Date.now(),
2644
- transaction: void 0,
2645
- operationQueue: Promise.resolve(),
2646
- depth: 1,
2647
- savepoints: [],
2648
- savepointCounter: 0
2649
- };
2650
- const abortController = new AbortController();
2651
- const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
2652
- startTimer?.unref?.();
2653
- const startTransactionPromise = this.driverAdapter.startTransaction(options.isolationLevel).catch(rethrowAsUserFacing);
2654
- transaction.transaction = await Promise.race([
2655
- startTransactionPromise.finally(() => clearTimeout(startTimer)),
2656
- once(abortController.signal, "abort").then(() => void 0)
2657
- ]);
2658
- this.transactions.set(transaction.id, transaction);
2659
- switch (transaction.status) {
2660
- case "waiting":
2661
- if (abortController.signal.aborted) {
2662
- void startTransactionPromise.then(async (tx) => {
2663
- if (tx.options.usePhantomQuery) {
2664
- await tx.rollback();
2665
- } else {
2666
- try {
2667
- await tx.executeRaw(ROLLBACK_QUERY());
2668
- } finally {
2669
- await tx.rollback();
2670
- }
2671
- }
2672
- }).catch((e) => debug("error in discarded transaction:", e));
2673
- await this.#closeTransaction(transaction, "timed_out");
2674
- throw new TransactionStartTimeoutError();
2935
+ const starting = trackStartingTransaction();
2936
+ const { abortController } = starting;
2937
+ this.#startingTransactions.add(starting);
2938
+ let discarding;
2939
+ try {
2940
+ const transaction = {
2941
+ id: await randomUUID(),
2942
+ status: "waiting",
2943
+ timer: void 0,
2944
+ timeout: options.timeout,
2945
+ startedAt: Date.now(),
2946
+ transaction: void 0,
2947
+ operationQueue: Promise.resolve(),
2948
+ depth: 1,
2949
+ savepoints: [],
2950
+ savepointCounter: 0
2951
+ };
2952
+ if (abortController.signal.aborted) {
2953
+ throw new TransactionStartTimeoutError();
2954
+ }
2955
+ const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
2956
+ startTimer?.unref?.();
2957
+ const startTransactionPromise = this.driverAdapter.startTransaction(options.isolationLevel).catch(rethrowAsUserFacing);
2958
+ transaction.transaction = await Promise.race([
2959
+ startTransactionPromise.finally(() => clearTimeout(startTimer)),
2960
+ once(abortController.signal, "abort").then(() => void 0)
2961
+ ]);
2962
+ this.transactions.set(transaction.id, transaction);
2963
+ switch (transaction.status) {
2964
+ case "waiting":
2965
+ if (abortController.signal.aborted) {
2966
+ transaction.transaction = void 0;
2967
+ discarding = this.#discardStartedTransaction(startTransactionPromise);
2968
+ await this.#closeTransaction(transaction, "timed_out");
2969
+ throw new TransactionStartTimeoutError();
2970
+ }
2971
+ transaction.status = "running";
2972
+ transaction.startedAt = Date.now();
2973
+ transaction.timer = this.#startTransactionTimeout(transaction.id, options.timeout);
2974
+ return { id: transaction.id };
2975
+ case "timed_out":
2976
+ case "running":
2977
+ case "committed":
2978
+ case "rolled_back":
2979
+ throw new TransactionInternalConsistencyError(
2980
+ `Transaction in invalid state ${transaction.status} although it just finished startup.`
2981
+ );
2982
+ default:
2983
+ return assertNever(transaction["status"], "Unknown transaction status.");
2984
+ }
2985
+ } finally {
2986
+ this.#startingTransactions.delete(starting);
2987
+ if (discarding) {
2988
+ void discarding.finally(starting.markSettled);
2989
+ } else {
2990
+ starting.markSettled();
2991
+ }
2992
+ }
2993
+ }
2994
+ /**
2995
+ * Rolls back a transaction whose start was abandoned, and releases its connection.
2996
+ *
2997
+ * The `startTransaction` promise may still be running in the background. If it eventually
2998
+ * succeeds, we need to roll back and release the connection to avoid leaking it and
2999
+ * exhausting the connection pool. For adapters that don't use phantom queries (e.g. pg/neon),
3000
+ * `rollback()` only releases the connection without sending SQL, so we send an explicit
3001
+ * ROLLBACK first; otherwise the connection returns to the pool mid-transaction because
3002
+ * `BEGIN` already ran on the wire during startup.
3003
+ *
3004
+ * Errors are only logged: the caller has already reported the failure that led here.
3005
+ */
3006
+ async #discardStartedTransaction(startTransactionPromise) {
3007
+ try {
3008
+ const tx = await startTransactionPromise;
3009
+ if (tx.options.usePhantomQuery) {
3010
+ await tx.rollback();
3011
+ } else {
3012
+ try {
3013
+ await tx.executeRaw(ROLLBACK_QUERY());
3014
+ } finally {
3015
+ await tx.rollback();
2675
3016
  }
2676
- transaction.status = "running";
2677
- transaction.timer = this.#startTransactionTimeout(transaction.id, options.timeout);
2678
- return { id: transaction.id };
2679
- case "timed_out":
2680
- case "running":
2681
- case "committed":
2682
- case "rolled_back":
2683
- throw new TransactionInternalConsistencyError(
2684
- `Transaction in invalid state ${transaction.status} although it just finished startup.`
2685
- );
2686
- default:
2687
- assertNever(transaction["status"], "Unknown transaction status.");
3017
+ }
3018
+ } catch (e) {
3019
+ debug2("error in discarded transaction:", e);
2688
3020
  }
2689
3021
  }
2690
3022
  async commitTransaction(transactionId) {
@@ -2748,7 +3080,7 @@ var TransactionManager = class {
2748
3080
  if (!transaction) {
2749
3081
  const closedTransaction = this.closedTransactions.find((tx) => tx.id === transactionId);
2750
3082
  if (closedTransaction) {
2751
- debug("Transaction already closed.", { transactionId, status: closedTransaction.status });
3083
+ debug2("Transaction already closed.", { transactionId, status: closedTransaction.status });
2752
3084
  switch (closedTransaction.status) {
2753
3085
  case "closing":
2754
3086
  case "waiting":
@@ -2765,7 +3097,7 @@ var TransactionManager = class {
2765
3097
  });
2766
3098
  }
2767
3099
  } else {
2768
- debug(`Transaction not found.`, transactionId);
3100
+ debug2(`Transaction not found.`, transactionId);
2769
3101
  throw new TransactionNotFoundError();
2770
3102
  }
2771
3103
  }
@@ -2775,16 +3107,21 @@ var TransactionManager = class {
2775
3107
  return transaction;
2776
3108
  }
2777
3109
  async cancelAllTransactions() {
2778
- await Promise.allSettled(
2779
- [...this.transactions.values()].map(
3110
+ const starting = [...this.#startingTransactions];
3111
+ for (const { abortController } of starting) {
3112
+ abortController.abort();
3113
+ }
3114
+ await Promise.allSettled([
3115
+ ...[...this.transactions.values()].map(
2780
3116
  (tx) => this.#runSerialized(tx, async () => {
2781
3117
  const current = this.transactions.get(tx.id);
2782
3118
  if (current) {
2783
3119
  await this.#closeTransaction(current, "rolled_back");
2784
3120
  }
2785
3121
  })
2786
- )
2787
- );
3122
+ ),
3123
+ ...starting.map(({ settled }) => settleWithin(settled, CANCEL_ROLLBACK_GRACE_MS))
3124
+ ]);
2788
3125
  }
2789
3126
  #nextSavepointName(transaction) {
2790
3127
  return `prisma_sp_${transaction.savepointCounter++}`;
@@ -2811,25 +3148,29 @@ var TransactionManager = class {
2811
3148
  }
2812
3149
  }
2813
3150
  #debugTransactionAlreadyClosedOnTimeout(transactionId) {
2814
- debug("Transaction already committed or rolled back when timeout happened.", transactionId);
3151
+ debug2("Transaction already committed or rolled back when timeout happened.", transactionId);
2815
3152
  }
2816
3153
  #startTransactionTimeout(transactionId, timeout) {
2817
3154
  const timeoutStartedAt = Date.now();
2818
3155
  const timer = createTimeoutIfDefined(async () => {
2819
- debug("Transaction timed out.", { transactionId, timeoutStartedAt, timeout });
2820
- const tx = this.transactions.get(transactionId);
2821
- if (!tx) {
2822
- this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
2823
- return;
2824
- }
2825
- await this.#runSerialized(tx, async () => {
2826
- const current = this.transactions.get(transactionId);
2827
- if (current && ["running", "waiting"].includes(current.status)) {
2828
- await this.#closeTransaction(current, "timed_out");
2829
- } else {
3156
+ try {
3157
+ debug2("Transaction timed out.", { transactionId, timeoutStartedAt, timeout });
3158
+ const tx = this.transactions.get(transactionId);
3159
+ if (!tx) {
2830
3160
  this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
3161
+ return;
2831
3162
  }
2832
- });
3163
+ await this.#runSerialized(tx, async () => {
3164
+ const current = this.transactions.get(transactionId);
3165
+ if (current && ["running", "waiting"].includes(current.status)) {
3166
+ await this.#closeTransaction(current, "timed_out");
3167
+ } else {
3168
+ this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
3169
+ }
3170
+ });
3171
+ } catch (error) {
3172
+ debug2("Error while closing timed-out transaction.", { transactionId, error });
3173
+ }
2833
3174
  }, timeout);
2834
3175
  timer?.unref?.();
2835
3176
  return timer;
@@ -2861,7 +3202,7 @@ var TransactionManager = class {
2861
3202
  }
2862
3203
  async #closeTransaction(tx, status) {
2863
3204
  const createClosingPromise = async () => {
2864
- debug("Closing transaction.", { transactionId: tx.id, status });
3205
+ debug2("Closing transaction.", { transactionId: tx.id, status });
2865
3206
  try {
2866
3207
  if (tx.transaction && status === "committed") {
2867
3208
  if (tx.transaction.options.usePhantomQuery) {
@@ -2937,6 +3278,14 @@ var TransactionManager = class {
2937
3278
  function createTimeoutIfDefined(cb, ms) {
2938
3279
  return ms !== void 0 ? setTimeout(cb, ms) : void 0;
2939
3280
  }
3281
+ function settleWithin(promise, timeout) {
3282
+ let timer;
3283
+ const deadline = new Promise((resolve) => {
3284
+ timer = setTimeout(resolve, timeout);
3285
+ timer?.unref?.();
3286
+ });
3287
+ return Promise.race([promise, deadline]).finally(() => clearTimeout(timer));
3288
+ }
2940
3289
  // Annotate the CommonJS export names for ESM import in node:
2941
3290
  0 && (module.exports = {
2942
3291
  DataMapperError,