@prisma/client-engine-runtime 7.10.0-dev.8 → 7.10.0-integration-prisma7-project-closeout.8

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.mjs CHANGED
@@ -3,6 +3,12 @@ import { Decimal as Decimal2 } from "@prisma/client-runtime-utils";
3
3
 
4
4
  // src/utils.ts
5
5
  import { Decimal } from "@prisma/client-runtime-utils";
6
+ function isUint8Array(value) {
7
+ return ArrayBuffer.isView(value) && Object.prototype.toString.call(value) === "[object Uint8Array]";
8
+ }
9
+ function isDate(value) {
10
+ return Object.prototype.toString.call(value) === "[object Date]";
11
+ }
6
12
  function assertNever(_, message) {
7
13
  throw new Error(message);
8
14
  }
@@ -21,11 +27,11 @@ function doKeysMatch(lhs, rhs) {
21
27
  const lhsDecimal = asDecimal(lhs[key]);
22
28
  const rhsDecimal = asDecimal(rhs[key]);
23
29
  return lhsDecimal && rhsDecimal && lhsDecimal.equals(rhsDecimal);
24
- } else if (lhs[key] instanceof Uint8Array || rhs[key] instanceof Uint8Array) {
30
+ } else if (isUint8Array(lhs[key]) || isUint8Array(rhs[key])) {
25
31
  const lhsBuffer = asBuffer(lhs[key]);
26
32
  const rhsBuffer = asBuffer(rhs[key]);
27
33
  return lhsBuffer && rhsBuffer && lhsBuffer.equals(rhsBuffer);
28
- } else if (lhs[key] instanceof Date || rhs[key] instanceof Date) {
34
+ } else if (isDate(lhs[key]) || isDate(rhs[key])) {
29
35
  return asDate(lhs[key])?.getTime() === asDate(rhs[key])?.getTime();
30
36
  } else if (typeof lhs[key] === "bigint" || typeof rhs[key] === "bigint") {
31
37
  return asBigInt(lhs[key]) === asBigInt(rhs[key]);
@@ -47,7 +53,7 @@ function asDecimal(value) {
47
53
  function asBuffer(value) {
48
54
  if (Buffer.isBuffer(value)) {
49
55
  return value;
50
- } else if (value instanceof Uint8Array) {
56
+ } else if (isUint8Array(value)) {
51
57
  return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
52
58
  } else if (typeof value === "string") {
53
59
  return Buffer.from(value, "base64");
@@ -56,7 +62,7 @@ function asBuffer(value) {
56
62
  }
57
63
  }
58
64
  function asDate(value) {
59
- if (value instanceof Date) {
65
+ if (isDate(value)) {
60
66
  return value;
61
67
  } else if (typeof value === "string" || typeof value === "number") {
62
68
  return new Date(value);
@@ -92,6 +98,16 @@ function safeJsonStringify(obj) {
92
98
  return val;
93
99
  });
94
100
  }
101
+ var MAX_PUSH_SPREAD_ARGS = 8192;
102
+ function appendToArray(target, source) {
103
+ if (source.length <= MAX_PUSH_SPREAD_ARGS) {
104
+ target.push(...source);
105
+ return;
106
+ }
107
+ for (let i = 0; i < source.length; i += MAX_PUSH_SPREAD_ARGS) {
108
+ target.push(...source.slice(i, i + MAX_PUSH_SPREAD_ARGS));
109
+ }
110
+ }
95
111
 
96
112
  // src/json-protocol.ts
97
113
  function normalizeJsonProtocolValues(result) {
@@ -173,10 +189,8 @@ function deserializeTaggedValue({ $type, value }) {
173
189
  switch ($type) {
174
190
  case "BigInt":
175
191
  return BigInt(value);
176
- case "Bytes": {
177
- const { buffer, byteOffset, byteLength } = Buffer.from(value, "base64");
178
- return new Uint8Array(buffer, byteOffset, byteLength);
179
- }
192
+ case "Bytes":
193
+ return new Uint8Array(Buffer.from(value, "base64"));
180
194
  case "DateTime":
181
195
  return new Date(value);
182
196
  case "Decimal":
@@ -224,7 +238,11 @@ function rethrowAsUserFacing(error) {
224
238
  const code = getErrorCode(error);
225
239
  const message = renderErrorMessage(error);
226
240
  if (code !== void 0 && message !== void 0) {
227
- throw new UserFacingError(message, code, { driverAdapterError: error });
241
+ const meta = { driverAdapterError: error };
242
+ if (error.cause.kind === "UniqueConstraintViolation" && error.cause.table) {
243
+ meta.table = error.cause.table;
244
+ }
245
+ throw new UserFacingError(message, code, meta);
228
246
  }
229
247
  if (isGenericDatabaseErrorKind(error.cause.kind)) {
230
248
  throw buildUnmappedDatabaseUserFacingError(error);
@@ -290,6 +308,7 @@ function getErrorCode(err) {
290
308
  case "UniqueConstraintViolation":
291
309
  return "P2002";
292
310
  case "ForeignKeyConstraintViolation":
311
+ case "RestrictViolation":
293
312
  return "P2003";
294
313
  case "InvalidInputValue":
295
314
  return "P2007";
@@ -362,6 +381,7 @@ function renderErrorMessage(err) {
362
381
  case "UniqueConstraintViolation":
363
382
  return `Unique constraint failed on the ${renderConstraint(err.cause.constraint)}`;
364
383
  case "ForeignKeyConstraintViolation":
384
+ case "RestrictViolation":
365
385
  return `Foreign key constraint violated on the ${renderConstraint(err.cause.constraint)}`;
366
386
  case "UnsupportedNativeDataType":
367
387
  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\`.`;
@@ -638,7 +658,7 @@ function mapValue(value, columnName, scalarType, enums) {
638
658
  throw new DataMapperError(`Expected a boolean in column '${columnName}', got ${typeof value}: ${value}`);
639
659
  }
640
660
  }
641
- if (Array.isArray(value) || value instanceof Uint8Array) {
661
+ if (Array.isArray(value) || isUint8Array(value)) {
642
662
  for (const byte of value) {
643
663
  if (byte !== 0) return true;
644
664
  }
@@ -655,7 +675,7 @@ function mapValue(value, columnName, scalarType, enums) {
655
675
  if (typeof value === "string") {
656
676
  return { $type: "DateTime", value: normalizeDateTime(value) };
657
677
  }
658
- if (typeof value === "number" || value instanceof Date) {
678
+ if (typeof value === "number" || isDate(value)) {
659
679
  return { $type: "DateTime", value };
660
680
  }
661
681
  throw new DataMapperError(`Expected a date in column '${columnName}', got ${typeof value}: ${value}`);
@@ -686,7 +706,7 @@ function mapValue(value, columnName, scalarType, enums) {
686
706
  if (Array.isArray(value)) {
687
707
  return { $type: "Bytes", value: Buffer.from(value).toString("base64") };
688
708
  }
689
- if (value instanceof Uint8Array) {
709
+ if (isUint8Array(value)) {
690
710
  return { $type: "Bytes", value: Buffer.from(value).toString("base64") };
691
711
  }
692
712
  throw new DataMapperError(`Expected a byte array in column '${columnName}', got ${typeof value}: ${value}`);
@@ -734,6 +754,7 @@ function normalizeDateTime(dt) {
734
754
  }
735
755
 
736
756
  // src/interpreter/query-interpreter.ts
757
+ import { Debug } from "@prisma/debug";
737
758
  import { klona as klona2 } from "klona";
738
759
 
739
760
  // src/sql-commenter.ts
@@ -1095,8 +1116,9 @@ function renderTemplateSql(fragments, placeholderFormat, params, argTypes) {
1095
1116
  if (fragment.type === "stringChunk") {
1096
1117
  continue;
1097
1118
  }
1098
- const length = flattenedParams.length;
1099
- const added = flattenedParams.push(...flattenedFragmentParams(fragment)) - length;
1119
+ const fragmentParams = Array.from(flattenedFragmentParams(fragment));
1120
+ const added = fragmentParams.length;
1121
+ appendToArray(flattenedParams, fragmentParams);
1100
1122
  if (fragment.argType.arity === "tuple") {
1101
1123
  if (added % fragment.argType.elements.length !== 0) {
1102
1124
  throw new Error(
@@ -1525,6 +1547,7 @@ function getErrorCode2(error) {
1525
1547
  }
1526
1548
 
1527
1549
  // src/interpreter/query-interpreter.ts
1550
+ var debug = Debug("prisma:client:queryInterpreter");
1528
1551
  var QueryInterpreter = class _QueryInterpreter {
1529
1552
  #onQuery;
1530
1553
  #generators = new GeneratorRegistry();
@@ -1559,17 +1582,27 @@ var QueryInterpreter = class _QueryInterpreter {
1559
1582
  });
1560
1583
  }
1561
1584
  async run(queryPlan, options) {
1562
- const { value } = await this.interpretNode(queryPlan, {
1563
- ...options,
1564
- generators: this.#generators.snapshot()
1565
- }).catch((e) => rethrowAsUserFacing(e));
1585
+ const generators = this.#generators.snapshot();
1586
+ const context = { ...options, generators };
1587
+ const purified = purifyQueryPlan(queryPlan, (node) => this.interpretNode(node, context))?.catch(
1588
+ (e) => rethrowAsUserFacing(e)
1589
+ );
1590
+ if (purified) {
1591
+ try {
1592
+ return this.#interpretPureNode(await purified, context.scope, generators).value;
1593
+ } catch (e) {
1594
+ rethrowAsUserFacing(e);
1595
+ }
1596
+ }
1597
+ const { value } = await this.interpretNode(queryPlan, context).catch((e) => rethrowAsUserFacing(e));
1566
1598
  return value;
1567
1599
  }
1568
1600
  async interpretNode(node, context) {
1569
1601
  switch (node.type) {
1570
1602
  case "value": {
1571
1603
  return {
1572
- value: evaluateArg(node.args, context.scope, context.generators)
1604
+ value: evaluateArg(node.args, context.scope, context.generators),
1605
+ lastInsertId: node.lastInsertId
1573
1606
  };
1574
1607
  }
1575
1608
  case "seq": {
@@ -1579,9 +1612,6 @@ var QueryInterpreter = class _QueryInterpreter {
1579
1612
  }
1580
1613
  return result ?? { value: void 0 };
1581
1614
  }
1582
- case "get": {
1583
- return { value: context.scope[node.args.name] };
1584
- }
1585
1615
  case "let": {
1586
1616
  const nestedScope = Object.create(context.scope);
1587
1617
  for (const binding of node.args.bindings) {
@@ -1590,15 +1620,6 @@ var QueryInterpreter = class _QueryInterpreter {
1590
1620
  }
1591
1621
  return this.interpretNode(node.args.expr, { ...context, scope: nestedScope });
1592
1622
  }
1593
- case "getFirstNonEmpty": {
1594
- for (const name of node.args.names) {
1595
- const value = context.scope[name];
1596
- if (!isEmpty(value)) {
1597
- return { value };
1598
- }
1599
- }
1600
- return { value: [] };
1601
- }
1602
1623
  case "concat": {
1603
1624
  const parts = await Promise.all(
1604
1625
  node.args.map((arg) => this.interpretNode(arg, context).then((res) => res.value))
@@ -1617,42 +1638,46 @@ var QueryInterpreter = class _QueryInterpreter {
1617
1638
  }
1618
1639
  case "execute": {
1619
1640
  const queries = renderQuery(node.args, context.scope, context.generators, this.#maxChunkSize());
1620
- let sum = 0;
1621
- for (const query of queries) {
1622
- const commentedQuery = applyComments(query, context.sqlCommenter);
1623
- sum += await this.#withQuerySpanAndEvent(
1624
- commentedQuery,
1625
- context.queryable,
1626
- () => context.queryable.executeRaw(cloneObject(commentedQuery)).catch(
1627
- (err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
1628
- )
1629
- );
1630
- }
1631
- return { value: sum };
1641
+ return this.#withChunkTransaction(queries.length, context, async (context2) => {
1642
+ let sum = 0;
1643
+ for (const query of queries) {
1644
+ const commentedQuery = applyComments(query, context2.sqlCommenter);
1645
+ sum += await this.#withQuerySpanAndEvent(
1646
+ commentedQuery,
1647
+ context2.queryable,
1648
+ () => context2.queryable.executeRaw(cloneObject(commentedQuery)).catch(
1649
+ (err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
1650
+ )
1651
+ );
1652
+ }
1653
+ return { value: sum };
1654
+ });
1632
1655
  }
1633
1656
  case "query": {
1634
1657
  const queries = renderQuery(node.args, context.scope, context.generators, this.#maxChunkSize());
1635
- let results;
1636
- for (const query of queries) {
1637
- const commentedQuery = applyComments(query, context.sqlCommenter);
1638
- const result = await this.#withQuerySpanAndEvent(
1639
- commentedQuery,
1640
- context.queryable,
1641
- () => context.queryable.queryRaw(cloneObject(commentedQuery)).catch(
1642
- (err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
1643
- )
1644
- );
1645
- if (results === void 0) {
1646
- results = result;
1647
- } else {
1648
- results.rows.push(...result.rows);
1649
- results.lastInsertId = result.lastInsertId;
1658
+ return this.#withChunkTransaction(queries.length, context, async (context2) => {
1659
+ let results;
1660
+ for (const query of queries) {
1661
+ const commentedQuery = applyComments(query, context2.sqlCommenter);
1662
+ const result = await this.#withQuerySpanAndEvent(
1663
+ commentedQuery,
1664
+ context2.queryable,
1665
+ () => context2.queryable.queryRaw(cloneObject(commentedQuery)).catch(
1666
+ (err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
1667
+ )
1668
+ );
1669
+ if (results === void 0) {
1670
+ results = result;
1671
+ } else {
1672
+ appendToArray(results.rows, result.rows);
1673
+ results.lastInsertId = result.lastInsertId;
1674
+ }
1650
1675
  }
1651
- }
1652
- return {
1653
- value: node.args.type === "rawSql" ? this.#rawSerializer(results) : this.#serializer(results),
1654
- lastInsertId: results?.lastInsertId
1655
- };
1676
+ return {
1677
+ value: node.args.type === "rawSql" ? this.#rawSerializer(results) : this.#serializer(results),
1678
+ lastInsertId: results?.lastInsertId
1679
+ };
1680
+ });
1656
1681
  }
1657
1682
  case "reverse": {
1658
1683
  const { value, lastInsertId } = await this.interpretNode(node.args, context);
@@ -1693,20 +1718,7 @@ var QueryInterpreter = class _QueryInterpreter {
1693
1718
  return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
1694
1719
  }
1695
1720
  case "transaction": {
1696
- if (!context.transactionManager.enabled) {
1697
- return this.interpretNode(node.args, context);
1698
- }
1699
- const transactionManager = context.transactionManager.manager;
1700
- const transactionInfo = await transactionManager.startInternalTransaction();
1701
- const transaction = await transactionManager.getTransaction(transactionInfo, "query");
1702
- try {
1703
- const value = await this.interpretNode(node.args, { ...context, queryable: transaction });
1704
- await transactionManager.commitTransaction(transactionInfo.id);
1705
- return value;
1706
- } catch (e) {
1707
- await transactionManager.rollbackTransaction(transactionInfo.id);
1708
- throw e;
1709
- }
1721
+ return this.#withInternalTransaction(context, (context2) => this.interpretNode(node.args, context2));
1710
1722
  }
1711
1723
  case "dataMap": {
1712
1724
  const { value, lastInsertId } = await this.interpretNode(node.args.expr, context);
@@ -1725,9 +1737,6 @@ var QueryInterpreter = class _QueryInterpreter {
1725
1737
  return await this.interpretNode(node.args.else, context);
1726
1738
  }
1727
1739
  }
1728
- case "unit": {
1729
- return { value: void 0 };
1730
- }
1731
1740
  case "diff": {
1732
1741
  const { value: from } = await this.interpretNode(node.args.from, context);
1733
1742
  const { value: to } = await this.interpretNode(node.args.to, context);
@@ -1757,10 +1766,185 @@ var QueryInterpreter = class _QueryInterpreter {
1757
1766
  }
1758
1767
  return { value: record, lastInsertId };
1759
1768
  }
1769
+ default:
1770
+ return this.#interpretPureNode(node, context.scope, context.generators);
1771
+ }
1772
+ }
1773
+ #interpretPureNode(node, scope, generators) {
1774
+ switch (node.type) {
1775
+ case "value": {
1776
+ return { value: evaluateArg(node.args, scope, generators), lastInsertId: node.lastInsertId };
1777
+ }
1778
+ case "seq": {
1779
+ let result;
1780
+ for (const arg of node.args) {
1781
+ result = this.#interpretPureNode(arg, scope, generators);
1782
+ }
1783
+ return result ?? { value: void 0 };
1784
+ }
1785
+ case "get": {
1786
+ return { value: scope[node.args.name] };
1787
+ }
1788
+ case "let": {
1789
+ const nestedScope = Object.create(scope);
1790
+ for (const binding of node.args.bindings) {
1791
+ const { value } = this.#interpretPureNode(binding.expr, nestedScope, generators);
1792
+ nestedScope[binding.name] = value;
1793
+ }
1794
+ return this.#interpretPureNode(node.args.expr, nestedScope, generators);
1795
+ }
1796
+ case "getFirstNonEmpty": {
1797
+ for (const name of node.args.names) {
1798
+ const value = scope[name];
1799
+ if (!isEmpty(value)) {
1800
+ return { value };
1801
+ }
1802
+ }
1803
+ return { value: [] };
1804
+ }
1805
+ case "concat": {
1806
+ const parts = node.args.map((arg) => this.#interpretPureNode(arg, scope, generators).value);
1807
+ return {
1808
+ value: parts.length > 0 ? parts.reduce((acc, part) => acc.concat(asList(part)), []) : []
1809
+ };
1810
+ }
1811
+ case "sum": {
1812
+ const parts = node.args.map((arg) => this.#interpretPureNode(arg, scope, generators).value);
1813
+ return {
1814
+ value: parts.length > 0 ? parts.reduce((acc, part) => asNumber2(acc) + asNumber2(part)) : 0
1815
+ };
1816
+ }
1817
+ case "reverse": {
1818
+ const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
1819
+ return { value: Array.isArray(value) ? value.reverse() : value, lastInsertId };
1820
+ }
1821
+ case "unique": {
1822
+ const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
1823
+ if (!Array.isArray(value)) {
1824
+ return { value, lastInsertId };
1825
+ }
1826
+ if (value.length > 1) {
1827
+ throw new Error(`Expected zero or one element, got ${value.length}`);
1828
+ }
1829
+ return { value: value[0] ?? null, lastInsertId };
1830
+ }
1831
+ case "required": {
1832
+ const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
1833
+ if (isEmpty(value)) {
1834
+ throw new Error("Required value is empty");
1835
+ }
1836
+ return { value, lastInsertId };
1837
+ }
1838
+ case "mapField": {
1839
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.records, scope, generators);
1840
+ return { value: mapField2(value, node.args.field), lastInsertId };
1841
+ }
1842
+ case "join": {
1843
+ const { value: parent, lastInsertId } = this.#interpretPureNode(node.args.parent, scope, generators);
1844
+ if (parent === null) {
1845
+ return { value: null, lastInsertId };
1846
+ }
1847
+ const children = node.args.children.map((joinExpr) => ({
1848
+ joinExpr,
1849
+ childRecords: this.#interpretPureNode(joinExpr.child, scope, generators).value
1850
+ }));
1851
+ return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
1852
+ }
1853
+ case "dataMap": {
1854
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1855
+ return { value: applyDataMap(value, node.args.structure, node.args.enums), lastInsertId };
1856
+ }
1857
+ case "validate": {
1858
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1859
+ performValidation(value, node.args.rules, node.args);
1860
+ return { value, lastInsertId };
1861
+ }
1862
+ case "if": {
1863
+ const { value } = this.#interpretPureNode(node.args.value, scope, generators);
1864
+ if (doesSatisfyRule(value, node.args.rule)) {
1865
+ return this.#interpretPureNode(node.args.then, scope, generators);
1866
+ } else {
1867
+ return this.#interpretPureNode(node.args.else, scope, generators);
1868
+ }
1869
+ }
1870
+ case "unit": {
1871
+ return { value: void 0 };
1872
+ }
1873
+ case "diff": {
1874
+ const { value: from } = this.#interpretPureNode(node.args.from, scope, generators);
1875
+ const { value: to } = this.#interpretPureNode(node.args.to, scope, generators);
1876
+ const keyGetter = (item) => item !== null ? getRecordKey(asRecord(item), node.args.fields) : null;
1877
+ const toSet = new Set(asList(to).map(keyGetter));
1878
+ return { value: asList(from).filter((item) => !toSet.has(keyGetter(item))) };
1879
+ }
1880
+ case "process": {
1881
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1882
+ const ops = cloneObject(node.args.operations);
1883
+ evaluateProcessingParameters(ops, scope, generators);
1884
+ return { value: processRecords(value, ops), lastInsertId };
1885
+ }
1886
+ case "initializeRecord": {
1887
+ const { lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1888
+ const record = {};
1889
+ for (const [key, initializer] of Object.entries(node.args.fields)) {
1890
+ record[key] = evalFieldInitializer(initializer, lastInsertId, scope, generators);
1891
+ }
1892
+ return { value: record, lastInsertId };
1893
+ }
1894
+ case "mapRecord": {
1895
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1896
+ const record = value === null ? {} : asRecord(value);
1897
+ for (const [key, entry] of Object.entries(node.args.fields)) {
1898
+ record[key] = evalFieldOperation(entry, record[key], scope, generators);
1899
+ }
1900
+ return { value: record, lastInsertId };
1901
+ }
1760
1902
  default:
1761
1903
  assertNever(node, `Unexpected node type: ${node.type}`);
1762
1904
  }
1763
1905
  }
1906
+ /**
1907
+ * Runs the statements of a `query` or `execute` node via `fn`, wrapping them in a
1908
+ * transaction when a chunkable statement was split into multiple queries at render time,
1909
+ * so that a partially applied write cannot be observed or left behind if a later chunk
1910
+ * fails. A single statement is atomic on its own, so it runs on the current context.
1911
+ */
1912
+ #withChunkTransaction(statementCount, context, fn) {
1913
+ if (statementCount <= 1) {
1914
+ return fn(context);
1915
+ }
1916
+ return this.#withInternalTransaction(context, fn);
1917
+ }
1918
+ /**
1919
+ * Runs `fn` with a context whose queryable is guaranteed to be a transaction, starting a
1920
+ * new internal transaction and committing or rolling it back around the call.
1921
+ *
1922
+ * A disabled transaction manager means the queryable already is a transaction: executors
1923
+ * pass `{ enabled: false }` when the plan runs inside an interactive transaction, and the
1924
+ * context handed to `fn` carries it for the duration of an internal transaction. In that
1925
+ * case `fn` runs on the current context, since the statements it issues are already
1926
+ * covered by the surrounding transaction.
1927
+ */
1928
+ async #withInternalTransaction(context, fn) {
1929
+ if (!context.transactionManager.enabled) {
1930
+ return fn(context);
1931
+ }
1932
+ const transactionManager = context.transactionManager.manager;
1933
+ const transactionInfo = await transactionManager.startInternalTransaction();
1934
+ const transaction = await transactionManager.getTransaction(transactionInfo, "query");
1935
+ try {
1936
+ const result = await fn({ ...context, queryable: transaction, transactionManager: { enabled: false } });
1937
+ await transactionManager.commitTransaction(transactionInfo.id);
1938
+ return result;
1939
+ } catch (e) {
1940
+ try {
1941
+ await transactionManager.rollbackTransaction(transactionInfo.id);
1942
+ } catch (rollbackError) {
1943
+ debug("failed to roll back an internal transaction", rollbackError);
1944
+ }
1945
+ throw e;
1946
+ }
1947
+ }
1764
1948
  #maxChunkSize() {
1765
1949
  if (this.#connectionInfo?.maxBindValues !== void 0) {
1766
1950
  return this.#connectionInfo.maxBindValues;
@@ -1934,6 +2118,112 @@ function evalFieldOperation(op, value, scope, generators) {
1934
2118
  assertNever(op, `Unexpected field operation type: ${op["type"]}`);
1935
2119
  }
1936
2120
  }
2121
+ function purifyQueryPlan(node, evalNode) {
2122
+ const impureNode = findUniqueUnconditionalImpureNode(node);
2123
+ if (!impureNode) {
2124
+ return void 0;
2125
+ }
2126
+ return evalNode(impureNode).then((result) => {
2127
+ const evaluated = {
2128
+ type: "value",
2129
+ args: result.value,
2130
+ lastInsertId: result.lastInsertId
2131
+ };
2132
+ const purified = replaceImpureNode(node, impureNode, evaluated);
2133
+ if (!purified) {
2134
+ throw new Error("Could not substitute the evaluated impure node into the query plan");
2135
+ }
2136
+ return purified;
2137
+ });
2138
+ }
2139
+ function replaceImpureNode(node, target, replacement) {
2140
+ if (node === target) {
2141
+ return replacement;
2142
+ }
2143
+ switch (node.type) {
2144
+ case "seq":
2145
+ case "sum":
2146
+ case "concat": {
2147
+ for (let i = 0; i < node.args.length; i++) {
2148
+ const child = replaceImpureNode(node.args[i], target, replacement);
2149
+ if (child) {
2150
+ return { ...node, args: node.args.map((arg, j) => j === i ? child : arg) };
2151
+ }
2152
+ }
2153
+ return void 0;
2154
+ }
2155
+ case "dataMap":
2156
+ case "validate":
2157
+ case "initializeRecord":
2158
+ case "mapRecord":
2159
+ case "process": {
2160
+ const expr = replaceImpureNode(node.args.expr, target, replacement);
2161
+ return expr && { ...node, args: { ...node.args, expr } };
2162
+ }
2163
+ case "mapField": {
2164
+ const records = replaceImpureNode(node.args.records, target, replacement);
2165
+ return records && { ...node, args: { ...node.args, records } };
2166
+ }
2167
+ case "reverse":
2168
+ case "unique":
2169
+ case "required": {
2170
+ const args = replaceImpureNode(node.args, target, replacement);
2171
+ return args && { ...node, args };
2172
+ }
2173
+ default:
2174
+ return void 0;
2175
+ }
2176
+ }
2177
+ function findUniqueUnconditionalImpureNode(node) {
2178
+ switch (node.type) {
2179
+ case "query":
2180
+ case "execute":
2181
+ return node;
2182
+ case "seq":
2183
+ case "sum":
2184
+ case "concat": {
2185
+ let found = void 0;
2186
+ for (const child of node.args) {
2187
+ const childFound = findUniqueUnconditionalImpureNode(child);
2188
+ if (childFound === null) {
2189
+ return null;
2190
+ }
2191
+ if (childFound) {
2192
+ if (found) {
2193
+ return null;
2194
+ }
2195
+ found = childFound;
2196
+ }
2197
+ }
2198
+ return found;
2199
+ }
2200
+ case "dataMap":
2201
+ case "validate":
2202
+ case "initializeRecord":
2203
+ case "mapRecord":
2204
+ case "process":
2205
+ return findUniqueUnconditionalImpureNode(node.args.expr);
2206
+ case "mapField":
2207
+ return findUniqueUnconditionalImpureNode(node.args.records);
2208
+ case "reverse":
2209
+ case "unique":
2210
+ case "required":
2211
+ return findUniqueUnconditionalImpureNode(node.args);
2212
+ case "let":
2213
+ case "join":
2214
+ case "diff":
2215
+ case "if":
2216
+ case "transaction":
2217
+ return null;
2218
+ case "value":
2219
+ case "get":
2220
+ case "getFirstNonEmpty":
2221
+ case "unit":
2222
+ return void 0;
2223
+ default:
2224
+ assertNever(node, `Unexpected node type: ${node.type}`);
2225
+ }
2226
+ }
1937
2227
  function applyComments(query, sqlCommenter) {
1938
2228
  if (!sqlCommenter || sqlCommenter.plugins.length === 0) {
1939
2229
  return query;
@@ -2432,7 +2722,7 @@ function normalizeValue(type, value) {
2432
2722
  }
2433
2723
 
2434
2724
  // src/transaction-manager/transaction-manager.ts
2435
- import { Debug } from "@prisma/debug";
2725
+ import { Debug as Debug2 } from "@prisma/debug";
2436
2726
 
2437
2727
  // src/crypto.ts
2438
2728
  async function getCrypto() {
@@ -2500,7 +2790,15 @@ var InvalidTransactionIsolationLevelError = class extends TransactionManagerErro
2500
2790
 
2501
2791
  // src/transaction-manager/transaction-manager.ts
2502
2792
  var MAX_CLOSED_TRANSACTIONS = 100;
2503
- var debug = Debug("prisma:client:transactionManager");
2793
+ var CANCEL_ROLLBACK_GRACE_MS = 2e3;
2794
+ function trackStartingTransaction() {
2795
+ let markSettled;
2796
+ const settled = new Promise((resolve) => {
2797
+ markSettled = resolve;
2798
+ });
2799
+ return { abortController: new AbortController(), settled, markSettled };
2800
+ }
2801
+ var debug2 = Debug2("prisma:client:transactionManager");
2504
2802
  var COMMIT_QUERY = () => ({ sql: "COMMIT", args: [], argTypes: [] });
2505
2803
  var ROLLBACK_QUERY = () => ({ sql: "ROLLBACK", args: [], argTypes: [] });
2506
2804
  var PHANTOM_COMMIT_QUERY = () => ({
@@ -2519,6 +2817,9 @@ var TransactionManager = class {
2519
2817
  // List of last closed transactions. Max MAX_CLOSED_TRANSACTIONS entries.
2520
2818
  // Used to provide better error messages than a generic "transaction not found".
2521
2819
  closedTransactions = [];
2820
+ // Transactions that are still being started. Tracked separately so that
2821
+ // `cancelAllTransactions` can reach them: they are not in `transactions` yet.
2822
+ #startingTransactions = /* @__PURE__ */ new Set();
2522
2823
  driverAdapter;
2523
2824
  transactionOptions;
2524
2825
  tracingHelper;
@@ -2582,56 +2883,91 @@ var TransactionManager = class {
2582
2883
  return { id: existing.id };
2583
2884
  });
2584
2885
  }
2585
- const transaction = {
2586
- id: await randomUUID(),
2587
- status: "waiting",
2588
- timer: void 0,
2589
- timeout: options.timeout,
2590
- startedAt: Date.now(),
2591
- transaction: void 0,
2592
- operationQueue: Promise.resolve(),
2593
- depth: 1,
2594
- savepoints: [],
2595
- savepointCounter: 0
2596
- };
2597
- const abortController = new AbortController();
2598
- const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
2599
- startTimer?.unref?.();
2600
- const startTransactionPromise = this.driverAdapter.startTransaction(options.isolationLevel).catch(rethrowAsUserFacing);
2601
- transaction.transaction = await Promise.race([
2602
- startTransactionPromise.finally(() => clearTimeout(startTimer)),
2603
- once(abortController.signal, "abort").then(() => void 0)
2604
- ]);
2605
- this.transactions.set(transaction.id, transaction);
2606
- switch (transaction.status) {
2607
- case "waiting":
2608
- if (abortController.signal.aborted) {
2609
- void startTransactionPromise.then(async (tx) => {
2610
- if (tx.options.usePhantomQuery) {
2611
- await tx.rollback();
2612
- } else {
2613
- try {
2614
- await tx.executeRaw(ROLLBACK_QUERY());
2615
- } finally {
2616
- await tx.rollback();
2617
- }
2618
- }
2619
- }).catch((e) => debug("error in discarded transaction:", e));
2620
- await this.#closeTransaction(transaction, "timed_out");
2621
- throw new TransactionStartTimeoutError();
2886
+ const starting = trackStartingTransaction();
2887
+ const { abortController } = starting;
2888
+ this.#startingTransactions.add(starting);
2889
+ let discarding;
2890
+ try {
2891
+ const transaction = {
2892
+ id: await randomUUID(),
2893
+ status: "waiting",
2894
+ timer: void 0,
2895
+ timeout: options.timeout,
2896
+ startedAt: Date.now(),
2897
+ transaction: void 0,
2898
+ operationQueue: Promise.resolve(),
2899
+ depth: 1,
2900
+ savepoints: [],
2901
+ savepointCounter: 0
2902
+ };
2903
+ if (abortController.signal.aborted) {
2904
+ throw new TransactionStartTimeoutError();
2905
+ }
2906
+ const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
2907
+ startTimer?.unref?.();
2908
+ const startTransactionPromise = this.driverAdapter.startTransaction(options.isolationLevel).catch(rethrowAsUserFacing);
2909
+ transaction.transaction = await Promise.race([
2910
+ startTransactionPromise.finally(() => clearTimeout(startTimer)),
2911
+ once(abortController.signal, "abort").then(() => void 0)
2912
+ ]);
2913
+ this.transactions.set(transaction.id, transaction);
2914
+ switch (transaction.status) {
2915
+ case "waiting":
2916
+ if (abortController.signal.aborted) {
2917
+ transaction.transaction = void 0;
2918
+ discarding = this.#discardStartedTransaction(startTransactionPromise);
2919
+ await this.#closeTransaction(transaction, "timed_out");
2920
+ throw new TransactionStartTimeoutError();
2921
+ }
2922
+ transaction.status = "running";
2923
+ transaction.startedAt = Date.now();
2924
+ transaction.timer = this.#startTransactionTimeout(transaction.id, options.timeout);
2925
+ return { id: transaction.id };
2926
+ case "timed_out":
2927
+ case "running":
2928
+ case "committed":
2929
+ case "rolled_back":
2930
+ throw new TransactionInternalConsistencyError(
2931
+ `Transaction in invalid state ${transaction.status} although it just finished startup.`
2932
+ );
2933
+ default:
2934
+ return assertNever(transaction["status"], "Unknown transaction status.");
2935
+ }
2936
+ } finally {
2937
+ this.#startingTransactions.delete(starting);
2938
+ if (discarding) {
2939
+ void discarding.finally(starting.markSettled);
2940
+ } else {
2941
+ starting.markSettled();
2942
+ }
2943
+ }
2944
+ }
2945
+ /**
2946
+ * Rolls back a transaction whose start was abandoned, and releases its connection.
2947
+ *
2948
+ * The `startTransaction` promise may still be running in the background. If it eventually
2949
+ * succeeds, we need to roll back and release the connection to avoid leaking it and
2950
+ * exhausting the connection pool. For adapters that don't use phantom queries (e.g. pg/neon),
2951
+ * `rollback()` only releases the connection without sending SQL, so we send an explicit
2952
+ * ROLLBACK first; otherwise the connection returns to the pool mid-transaction because
2953
+ * `BEGIN` already ran on the wire during startup.
2954
+ *
2955
+ * Errors are only logged: the caller has already reported the failure that led here.
2956
+ */
2957
+ async #discardStartedTransaction(startTransactionPromise) {
2958
+ try {
2959
+ const tx = await startTransactionPromise;
2960
+ if (tx.options.usePhantomQuery) {
2961
+ await tx.rollback();
2962
+ } else {
2963
+ try {
2964
+ await tx.executeRaw(ROLLBACK_QUERY());
2965
+ } finally {
2966
+ await tx.rollback();
2622
2967
  }
2623
- transaction.status = "running";
2624
- transaction.timer = this.#startTransactionTimeout(transaction.id, options.timeout);
2625
- return { id: transaction.id };
2626
- case "timed_out":
2627
- case "running":
2628
- case "committed":
2629
- case "rolled_back":
2630
- throw new TransactionInternalConsistencyError(
2631
- `Transaction in invalid state ${transaction.status} although it just finished startup.`
2632
- );
2633
- default:
2634
- assertNever(transaction["status"], "Unknown transaction status.");
2968
+ }
2969
+ } catch (e) {
2970
+ debug2("error in discarded transaction:", e);
2635
2971
  }
2636
2972
  }
2637
2973
  async commitTransaction(transactionId) {
@@ -2695,7 +3031,7 @@ var TransactionManager = class {
2695
3031
  if (!transaction) {
2696
3032
  const closedTransaction = this.closedTransactions.find((tx) => tx.id === transactionId);
2697
3033
  if (closedTransaction) {
2698
- debug("Transaction already closed.", { transactionId, status: closedTransaction.status });
3034
+ debug2("Transaction already closed.", { transactionId, status: closedTransaction.status });
2699
3035
  switch (closedTransaction.status) {
2700
3036
  case "closing":
2701
3037
  case "waiting":
@@ -2712,7 +3048,7 @@ var TransactionManager = class {
2712
3048
  });
2713
3049
  }
2714
3050
  } else {
2715
- debug(`Transaction not found.`, transactionId);
3051
+ debug2(`Transaction not found.`, transactionId);
2716
3052
  throw new TransactionNotFoundError();
2717
3053
  }
2718
3054
  }
@@ -2722,16 +3058,21 @@ var TransactionManager = class {
2722
3058
  return transaction;
2723
3059
  }
2724
3060
  async cancelAllTransactions() {
2725
- await Promise.allSettled(
2726
- [...this.transactions.values()].map(
3061
+ const starting = [...this.#startingTransactions];
3062
+ for (const { abortController } of starting) {
3063
+ abortController.abort();
3064
+ }
3065
+ await Promise.allSettled([
3066
+ ...[...this.transactions.values()].map(
2727
3067
  (tx) => this.#runSerialized(tx, async () => {
2728
3068
  const current = this.transactions.get(tx.id);
2729
3069
  if (current) {
2730
3070
  await this.#closeTransaction(current, "rolled_back");
2731
3071
  }
2732
3072
  })
2733
- )
2734
- );
3073
+ ),
3074
+ ...starting.map(({ settled }) => settleWithin(settled, CANCEL_ROLLBACK_GRACE_MS))
3075
+ ]);
2735
3076
  }
2736
3077
  #nextSavepointName(transaction) {
2737
3078
  return `prisma_sp_${transaction.savepointCounter++}`;
@@ -2758,25 +3099,29 @@ var TransactionManager = class {
2758
3099
  }
2759
3100
  }
2760
3101
  #debugTransactionAlreadyClosedOnTimeout(transactionId) {
2761
- debug("Transaction already committed or rolled back when timeout happened.", transactionId);
3102
+ debug2("Transaction already committed or rolled back when timeout happened.", transactionId);
2762
3103
  }
2763
3104
  #startTransactionTimeout(transactionId, timeout) {
2764
3105
  const timeoutStartedAt = Date.now();
2765
3106
  const timer = createTimeoutIfDefined(async () => {
2766
- debug("Transaction timed out.", { transactionId, timeoutStartedAt, timeout });
2767
- const tx = this.transactions.get(transactionId);
2768
- if (!tx) {
2769
- this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
2770
- return;
2771
- }
2772
- await this.#runSerialized(tx, async () => {
2773
- const current = this.transactions.get(transactionId);
2774
- if (current && ["running", "waiting"].includes(current.status)) {
2775
- await this.#closeTransaction(current, "timed_out");
2776
- } else {
3107
+ try {
3108
+ debug2("Transaction timed out.", { transactionId, timeoutStartedAt, timeout });
3109
+ const tx = this.transactions.get(transactionId);
3110
+ if (!tx) {
2777
3111
  this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
3112
+ return;
2778
3113
  }
2779
- });
3114
+ await this.#runSerialized(tx, async () => {
3115
+ const current = this.transactions.get(transactionId);
3116
+ if (current && ["running", "waiting"].includes(current.status)) {
3117
+ await this.#closeTransaction(current, "timed_out");
3118
+ } else {
3119
+ this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
3120
+ }
3121
+ });
3122
+ } catch (error) {
3123
+ debug2("Error while closing timed-out transaction.", { transactionId, error });
3124
+ }
2780
3125
  }, timeout);
2781
3126
  timer?.unref?.();
2782
3127
  return timer;
@@ -2808,7 +3153,7 @@ var TransactionManager = class {
2808
3153
  }
2809
3154
  async #closeTransaction(tx, status) {
2810
3155
  const createClosingPromise = async () => {
2811
- debug("Closing transaction.", { transactionId: tx.id, status });
3156
+ debug2("Closing transaction.", { transactionId: tx.id, status });
2812
3157
  try {
2813
3158
  if (tx.transaction && status === "committed") {
2814
3159
  if (tx.transaction.options.usePhantomQuery) {
@@ -2884,6 +3229,14 @@ var TransactionManager = class {
2884
3229
  function createTimeoutIfDefined(cb, ms) {
2885
3230
  return ms !== void 0 ? setTimeout(cb, ms) : void 0;
2886
3231
  }
3232
+ function settleWithin(promise, timeout) {
3233
+ let timer;
3234
+ const deadline = new Promise((resolve) => {
3235
+ timer = setTimeout(resolve, timeout);
3236
+ timer?.unref?.();
3237
+ });
3238
+ return Promise.race([promise, deadline]).finally(() => clearTimeout(timer));
3239
+ }
2887
3240
  export {
2888
3241
  DataMapperError,
2889
3242
  QueryInterpreter,