@prisma/client-engine-runtime 7.10.0-dev.3 → 7.10.0-dev.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +123 -91
- package/dist/index.d.ts +123 -91
- package/dist/index.js +384 -80
- package/dist/index.mjs +384 -80
- package/dist/interpreter/query-interpreter.d.ts +20 -1
- package/dist/interpreter/query-interpreter.test.d.ts +1 -0
- package/dist/query-plan.d.ts +61 -31
- package/dist/utils.d.ts +5 -0
- package/package.json +10 -10
package/dist/index.mjs
CHANGED
|
@@ -92,6 +92,16 @@ function safeJsonStringify(obj) {
|
|
|
92
92
|
return val;
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
|
+
var MAX_PUSH_SPREAD_ARGS = 8192;
|
|
96
|
+
function appendToArray(target, source) {
|
|
97
|
+
if (source.length <= MAX_PUSH_SPREAD_ARGS) {
|
|
98
|
+
target.push(...source);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for (let i = 0; i < source.length; i += MAX_PUSH_SPREAD_ARGS) {
|
|
102
|
+
target.push(...source.slice(i, i + MAX_PUSH_SPREAD_ARGS));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
95
105
|
|
|
96
106
|
// src/json-protocol.ts
|
|
97
107
|
function normalizeJsonProtocolValues(result) {
|
|
@@ -173,10 +183,8 @@ function deserializeTaggedValue({ $type, value }) {
|
|
|
173
183
|
switch ($type) {
|
|
174
184
|
case "BigInt":
|
|
175
185
|
return BigInt(value);
|
|
176
|
-
case "Bytes":
|
|
177
|
-
|
|
178
|
-
return new Uint8Array(buffer, byteOffset, byteLength);
|
|
179
|
-
}
|
|
186
|
+
case "Bytes":
|
|
187
|
+
return new Uint8Array(Buffer.from(value, "base64"));
|
|
180
188
|
case "DateTime":
|
|
181
189
|
return new Date(value);
|
|
182
190
|
case "Decimal":
|
|
@@ -290,6 +298,7 @@ function getErrorCode(err) {
|
|
|
290
298
|
case "UniqueConstraintViolation":
|
|
291
299
|
return "P2002";
|
|
292
300
|
case "ForeignKeyConstraintViolation":
|
|
301
|
+
case "RestrictViolation":
|
|
293
302
|
return "P2003";
|
|
294
303
|
case "InvalidInputValue":
|
|
295
304
|
return "P2007";
|
|
@@ -362,6 +371,7 @@ function renderErrorMessage(err) {
|
|
|
362
371
|
case "UniqueConstraintViolation":
|
|
363
372
|
return `Unique constraint failed on the ${renderConstraint(err.cause.constraint)}`;
|
|
364
373
|
case "ForeignKeyConstraintViolation":
|
|
374
|
+
case "RestrictViolation":
|
|
365
375
|
return `Foreign key constraint violated on the ${renderConstraint(err.cause.constraint)}`;
|
|
366
376
|
case "UnsupportedNativeDataType":
|
|
367
377
|
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\`.`;
|
|
@@ -1095,8 +1105,9 @@ function renderTemplateSql(fragments, placeholderFormat, params, argTypes) {
|
|
|
1095
1105
|
if (fragment.type === "stringChunk") {
|
|
1096
1106
|
continue;
|
|
1097
1107
|
}
|
|
1098
|
-
const
|
|
1099
|
-
const added =
|
|
1108
|
+
const fragmentParams = Array.from(flattenedFragmentParams(fragment));
|
|
1109
|
+
const added = fragmentParams.length;
|
|
1110
|
+
appendToArray(flattenedParams, fragmentParams);
|
|
1100
1111
|
if (fragment.argType.arity === "tuple") {
|
|
1101
1112
|
if (added % fragment.argType.elements.length !== 0) {
|
|
1102
1113
|
throw new Error(
|
|
@@ -1559,17 +1570,27 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1559
1570
|
});
|
|
1560
1571
|
}
|
|
1561
1572
|
async run(queryPlan, options) {
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
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));
|
|
1566
1586
|
return value;
|
|
1567
1587
|
}
|
|
1568
1588
|
async interpretNode(node, context) {
|
|
1569
1589
|
switch (node.type) {
|
|
1570
1590
|
case "value": {
|
|
1571
1591
|
return {
|
|
1572
|
-
value: evaluateArg(node.args, context.scope, context.generators)
|
|
1592
|
+
value: evaluateArg(node.args, context.scope, context.generators),
|
|
1593
|
+
lastInsertId: node.lastInsertId
|
|
1573
1594
|
};
|
|
1574
1595
|
}
|
|
1575
1596
|
case "seq": {
|
|
@@ -1579,9 +1600,6 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1579
1600
|
}
|
|
1580
1601
|
return result ?? { value: void 0 };
|
|
1581
1602
|
}
|
|
1582
|
-
case "get": {
|
|
1583
|
-
return { value: context.scope[node.args.name] };
|
|
1584
|
-
}
|
|
1585
1603
|
case "let": {
|
|
1586
1604
|
const nestedScope = Object.create(context.scope);
|
|
1587
1605
|
for (const binding of node.args.bindings) {
|
|
@@ -1590,15 +1608,6 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1590
1608
|
}
|
|
1591
1609
|
return this.interpretNode(node.args.expr, { ...context, scope: nestedScope });
|
|
1592
1610
|
}
|
|
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
1611
|
case "concat": {
|
|
1603
1612
|
const parts = await Promise.all(
|
|
1604
1613
|
node.args.map((arg) => this.interpretNode(arg, context).then((res) => res.value))
|
|
@@ -1645,7 +1654,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1645
1654
|
if (results === void 0) {
|
|
1646
1655
|
results = result;
|
|
1647
1656
|
} else {
|
|
1648
|
-
results.rows
|
|
1657
|
+
appendToArray(results.rows, result.rows);
|
|
1649
1658
|
results.lastInsertId = result.lastInsertId;
|
|
1650
1659
|
}
|
|
1651
1660
|
}
|
|
@@ -1725,9 +1734,6 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1725
1734
|
return await this.interpretNode(node.args.else, context);
|
|
1726
1735
|
}
|
|
1727
1736
|
}
|
|
1728
|
-
case "unit": {
|
|
1729
|
-
return { value: void 0 };
|
|
1730
|
-
}
|
|
1731
1737
|
case "diff": {
|
|
1732
1738
|
const { value: from } = await this.interpretNode(node.args.from, context);
|
|
1733
1739
|
const { value: to } = await this.interpretNode(node.args.to, context);
|
|
@@ -1757,6 +1763,139 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1757
1763
|
}
|
|
1758
1764
|
return { value: record, lastInsertId };
|
|
1759
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
|
+
}
|
|
1760
1899
|
default:
|
|
1761
1900
|
assertNever(node, `Unexpected node type: ${node.type}`);
|
|
1762
1901
|
}
|
|
@@ -1934,6 +2073,112 @@ function evalFieldOperation(op, value, scope, generators) {
|
|
|
1934
2073
|
assertNever(op, `Unexpected field operation type: ${op["type"]}`);
|
|
1935
2074
|
}
|
|
1936
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
|
+
}
|
|
1937
2182
|
function applyComments(query, sqlCommenter) {
|
|
1938
2183
|
if (!sqlCommenter || sqlCommenter.plugins.length === 0) {
|
|
1939
2184
|
return query;
|
|
@@ -2500,6 +2745,14 @@ var InvalidTransactionIsolationLevelError = class extends TransactionManagerErro
|
|
|
2500
2745
|
|
|
2501
2746
|
// src/transaction-manager/transaction-manager.ts
|
|
2502
2747
|
var MAX_CLOSED_TRANSACTIONS = 100;
|
|
2748
|
+
var CANCEL_ROLLBACK_GRACE_MS = 2e3;
|
|
2749
|
+
function trackStartingTransaction() {
|
|
2750
|
+
let markSettled;
|
|
2751
|
+
const settled = new Promise((resolve) => {
|
|
2752
|
+
markSettled = resolve;
|
|
2753
|
+
});
|
|
2754
|
+
return { abortController: new AbortController(), settled, markSettled };
|
|
2755
|
+
}
|
|
2503
2756
|
var debug = Debug("prisma:client:transactionManager");
|
|
2504
2757
|
var COMMIT_QUERY = () => ({ sql: "COMMIT", args: [], argTypes: [] });
|
|
2505
2758
|
var ROLLBACK_QUERY = () => ({ sql: "ROLLBACK", args: [], argTypes: [] });
|
|
@@ -2519,6 +2772,9 @@ var TransactionManager = class {
|
|
|
2519
2772
|
// List of last closed transactions. Max MAX_CLOSED_TRANSACTIONS entries.
|
|
2520
2773
|
// Used to provide better error messages than a generic "transaction not found".
|
|
2521
2774
|
closedTransactions = [];
|
|
2775
|
+
// Transactions that are still being started. Tracked separately so that
|
|
2776
|
+
// `cancelAllTransactions` can reach them: they are not in `transactions` yet.
|
|
2777
|
+
#startingTransactions = /* @__PURE__ */ new Set();
|
|
2522
2778
|
driverAdapter;
|
|
2523
2779
|
transactionOptions;
|
|
2524
2780
|
tracingHelper;
|
|
@@ -2582,56 +2838,91 @@ var TransactionManager = class {
|
|
|
2582
2838
|
return { id: existing.id };
|
|
2583
2839
|
});
|
|
2584
2840
|
}
|
|
2585
|
-
const
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2841
|
+
const starting = trackStartingTransaction();
|
|
2842
|
+
const { abortController } = starting;
|
|
2843
|
+
this.#startingTransactions.add(starting);
|
|
2844
|
+
let discarding;
|
|
2845
|
+
try {
|
|
2846
|
+
const transaction = {
|
|
2847
|
+
id: await randomUUID(),
|
|
2848
|
+
status: "waiting",
|
|
2849
|
+
timer: void 0,
|
|
2850
|
+
timeout: options.timeout,
|
|
2851
|
+
startedAt: Date.now(),
|
|
2852
|
+
transaction: void 0,
|
|
2853
|
+
operationQueue: Promise.resolve(),
|
|
2854
|
+
depth: 1,
|
|
2855
|
+
savepoints: [],
|
|
2856
|
+
savepointCounter: 0
|
|
2857
|
+
};
|
|
2858
|
+
if (abortController.signal.aborted) {
|
|
2859
|
+
throw new TransactionStartTimeoutError();
|
|
2860
|
+
}
|
|
2861
|
+
const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
|
|
2862
|
+
startTimer?.unref?.();
|
|
2863
|
+
const startTransactionPromise = this.driverAdapter.startTransaction(options.isolationLevel).catch(rethrowAsUserFacing);
|
|
2864
|
+
transaction.transaction = await Promise.race([
|
|
2865
|
+
startTransactionPromise.finally(() => clearTimeout(startTimer)),
|
|
2866
|
+
once(abortController.signal, "abort").then(() => void 0)
|
|
2867
|
+
]);
|
|
2868
|
+
this.transactions.set(transaction.id, transaction);
|
|
2869
|
+
switch (transaction.status) {
|
|
2870
|
+
case "waiting":
|
|
2871
|
+
if (abortController.signal.aborted) {
|
|
2872
|
+
transaction.transaction = void 0;
|
|
2873
|
+
discarding = this.#discardStartedTransaction(startTransactionPromise);
|
|
2874
|
+
await this.#closeTransaction(transaction, "timed_out");
|
|
2875
|
+
throw new TransactionStartTimeoutError();
|
|
2876
|
+
}
|
|
2877
|
+
transaction.status = "running";
|
|
2878
|
+
transaction.startedAt = Date.now();
|
|
2879
|
+
transaction.timer = this.#startTransactionTimeout(transaction.id, options.timeout);
|
|
2880
|
+
return { id: transaction.id };
|
|
2881
|
+
case "timed_out":
|
|
2882
|
+
case "running":
|
|
2883
|
+
case "committed":
|
|
2884
|
+
case "rolled_back":
|
|
2885
|
+
throw new TransactionInternalConsistencyError(
|
|
2886
|
+
`Transaction in invalid state ${transaction.status} although it just finished startup.`
|
|
2887
|
+
);
|
|
2888
|
+
default:
|
|
2889
|
+
return assertNever(transaction["status"], "Unknown transaction status.");
|
|
2890
|
+
}
|
|
2891
|
+
} finally {
|
|
2892
|
+
this.#startingTransactions.delete(starting);
|
|
2893
|
+
if (discarding) {
|
|
2894
|
+
void discarding.finally(starting.markSettled);
|
|
2895
|
+
} else {
|
|
2896
|
+
starting.markSettled();
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
/**
|
|
2901
|
+
* Rolls back a transaction whose start was abandoned, and releases its connection.
|
|
2902
|
+
*
|
|
2903
|
+
* The `startTransaction` promise may still be running in the background. If it eventually
|
|
2904
|
+
* succeeds, we need to roll back and release the connection to avoid leaking it and
|
|
2905
|
+
* exhausting the connection pool. For adapters that don't use phantom queries (e.g. pg/neon),
|
|
2906
|
+
* `rollback()` only releases the connection without sending SQL, so we send an explicit
|
|
2907
|
+
* ROLLBACK first; otherwise the connection returns to the pool mid-transaction because
|
|
2908
|
+
* `BEGIN` already ran on the wire during startup.
|
|
2909
|
+
*
|
|
2910
|
+
* Errors are only logged: the caller has already reported the failure that led here.
|
|
2911
|
+
*/
|
|
2912
|
+
async #discardStartedTransaction(startTransactionPromise) {
|
|
2913
|
+
try {
|
|
2914
|
+
const tx = await startTransactionPromise;
|
|
2915
|
+
if (tx.options.usePhantomQuery) {
|
|
2916
|
+
await tx.rollback();
|
|
2917
|
+
} else {
|
|
2918
|
+
try {
|
|
2919
|
+
await tx.executeRaw(ROLLBACK_QUERY());
|
|
2920
|
+
} finally {
|
|
2921
|
+
await tx.rollback();
|
|
2622
2922
|
}
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
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.");
|
|
2923
|
+
}
|
|
2924
|
+
} catch (e) {
|
|
2925
|
+
debug("error in discarded transaction:", e);
|
|
2635
2926
|
}
|
|
2636
2927
|
}
|
|
2637
2928
|
async commitTransaction(transactionId) {
|
|
@@ -2722,16 +3013,21 @@ var TransactionManager = class {
|
|
|
2722
3013
|
return transaction;
|
|
2723
3014
|
}
|
|
2724
3015
|
async cancelAllTransactions() {
|
|
2725
|
-
|
|
2726
|
-
|
|
3016
|
+
const starting = [...this.#startingTransactions];
|
|
3017
|
+
for (const { abortController } of starting) {
|
|
3018
|
+
abortController.abort();
|
|
3019
|
+
}
|
|
3020
|
+
await Promise.allSettled([
|
|
3021
|
+
...[...this.transactions.values()].map(
|
|
2727
3022
|
(tx) => this.#runSerialized(tx, async () => {
|
|
2728
3023
|
const current = this.transactions.get(tx.id);
|
|
2729
3024
|
if (current) {
|
|
2730
3025
|
await this.#closeTransaction(current, "rolled_back");
|
|
2731
3026
|
}
|
|
2732
3027
|
})
|
|
2733
|
-
)
|
|
2734
|
-
|
|
3028
|
+
),
|
|
3029
|
+
...starting.map(({ settled }) => settleWithin(settled, CANCEL_ROLLBACK_GRACE_MS))
|
|
3030
|
+
]);
|
|
2735
3031
|
}
|
|
2736
3032
|
#nextSavepointName(transaction) {
|
|
2737
3033
|
return `prisma_sp_${transaction.savepointCounter++}`;
|
|
@@ -2884,6 +3180,14 @@ var TransactionManager = class {
|
|
|
2884
3180
|
function createTimeoutIfDefined(cb, ms) {
|
|
2885
3181
|
return ms !== void 0 ? setTimeout(cb, ms) : void 0;
|
|
2886
3182
|
}
|
|
3183
|
+
function settleWithin(promise, timeout) {
|
|
3184
|
+
let timer;
|
|
3185
|
+
const deadline = new Promise((resolve) => {
|
|
3186
|
+
timer = setTimeout(resolve, timeout);
|
|
3187
|
+
timer?.unref?.();
|
|
3188
|
+
});
|
|
3189
|
+
return Promise.race([promise, deadline]).finally(() => clearTimeout(timer));
|
|
3190
|
+
}
|
|
2887
3191
|
export {
|
|
2888
3192
|
DataMapperError,
|
|
2889
3193
|
QueryInterpreter,
|
|
@@ -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 {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|