@prisma/client-engine-runtime 7.5.0-dev.5 → 7.5.0-dev.50
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/events.d.ts +1 -1
- package/dist/index.d.mts +24 -10
- package/dist/index.d.ts +24 -10
- package/dist/index.js +232 -32
- package/dist/index.mjs +231 -31
- package/dist/interpreter/in-memory-processing.d.ts +1 -1
- package/dist/interpreter/query-interpreter.d.ts +2 -1
- package/dist/interpreter/render-query.d.ts +2 -1
- package/dist/interpreter/validation.d.ts +2 -1
- package/dist/json-protocol.d.ts +6 -2
- package/dist/query-plan.d.ts +10 -6
- package/dist/tracing.d.ts +2 -1
- package/dist/transaction-manager/transaction.d.ts +1 -0
- package/dist/utils.d.ts +6 -0
- package/package.json +7 -10
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
UserFacingError: () => UserFacingError,
|
|
38
38
|
applySqlCommenters: () => applySqlCommenters,
|
|
39
39
|
convertCompactedRows: () => convertCompactedRows,
|
|
40
|
-
|
|
40
|
+
deserializeJsonObject: () => deserializeJsonObject,
|
|
41
41
|
doKeysMatch: () => doKeysMatch,
|
|
42
42
|
isDeepStrictEqual: () => isDeepStrictEqual,
|
|
43
43
|
isPrismaValueGenerator: () => isPrismaValueGenerator,
|
|
@@ -170,7 +170,10 @@ function normalizeJsonProtocolValues(result) {
|
|
|
170
170
|
function isTaggedValue(value) {
|
|
171
171
|
return value !== null && typeof value == "object" && typeof value["$type"] === "string";
|
|
172
172
|
}
|
|
173
|
-
function normalizeTaggedValue({
|
|
173
|
+
function normalizeTaggedValue({
|
|
174
|
+
$type,
|
|
175
|
+
value
|
|
176
|
+
}) {
|
|
174
177
|
switch ($type) {
|
|
175
178
|
case "BigInt":
|
|
176
179
|
return { $type, value: String(value) };
|
|
@@ -182,6 +185,12 @@ function normalizeTaggedValue({ $type, value }) {
|
|
|
182
185
|
return { $type, value: String(new import_client_runtime_utils2.Decimal(value)) };
|
|
183
186
|
case "Json":
|
|
184
187
|
return { $type, value: JSON.stringify(JSON.parse(value)) };
|
|
188
|
+
case "Raw":
|
|
189
|
+
return { $type, value };
|
|
190
|
+
case "FieldRef":
|
|
191
|
+
return { $type, value };
|
|
192
|
+
case "Enum":
|
|
193
|
+
return { $type, value };
|
|
185
194
|
default:
|
|
186
195
|
assertNever(value, "Unknown tagged value");
|
|
187
196
|
}
|
|
@@ -193,12 +202,12 @@ function mapObjectValues(object, mapper) {
|
|
|
193
202
|
}
|
|
194
203
|
return result;
|
|
195
204
|
}
|
|
196
|
-
function
|
|
205
|
+
function deserializeJsonObject(result) {
|
|
197
206
|
if (result === null) {
|
|
198
207
|
return result;
|
|
199
208
|
}
|
|
200
209
|
if (Array.isArray(result)) {
|
|
201
|
-
return result.map(
|
|
210
|
+
return result.map(deserializeJsonObject);
|
|
202
211
|
}
|
|
203
212
|
if (typeof result === "object") {
|
|
204
213
|
if (isTaggedValue(result)) {
|
|
@@ -207,7 +216,7 @@ function deserializeJsonResponse(result) {
|
|
|
207
216
|
if (result.constructor !== null && result.constructor.name !== "Object") {
|
|
208
217
|
return result;
|
|
209
218
|
}
|
|
210
|
-
return mapObjectValues(result,
|
|
219
|
+
return mapObjectValues(result, deserializeJsonObject);
|
|
211
220
|
}
|
|
212
221
|
return result;
|
|
213
222
|
}
|
|
@@ -225,6 +234,12 @@ function deserializeTaggedValue({ $type, value }) {
|
|
|
225
234
|
return new import_client_runtime_utils2.Decimal(value);
|
|
226
235
|
case "Json":
|
|
227
236
|
return JSON.parse(value);
|
|
237
|
+
case "Raw":
|
|
238
|
+
return value;
|
|
239
|
+
case "FieldRef":
|
|
240
|
+
throw new Error("FieldRef tagged values cannot be deserialized to JavaScript values");
|
|
241
|
+
case "Enum":
|
|
242
|
+
return value;
|
|
228
243
|
default:
|
|
229
244
|
assertNever(value, "Unknown tagged value");
|
|
230
245
|
}
|
|
@@ -456,7 +471,7 @@ function resolveArgPlaceholders(args, placeholderValues) {
|
|
|
456
471
|
function convertCompactedRows(rows, compiledBatch, placeholderValues = {}) {
|
|
457
472
|
const keysPerRow = rows.map(
|
|
458
473
|
(item) => compiledBatch.keys.reduce((acc, key) => {
|
|
459
|
-
acc[key] =
|
|
474
|
+
acc[key] = deserializeJsonObject(item[key]);
|
|
460
475
|
return acc;
|
|
461
476
|
}, {})
|
|
462
477
|
);
|
|
@@ -742,6 +757,9 @@ function normalizeDateTime(dt) {
|
|
|
742
757
|
return dtWithTz;
|
|
743
758
|
}
|
|
744
759
|
|
|
760
|
+
// src/interpreter/query-interpreter.ts
|
|
761
|
+
var import_klona2 = require("klona");
|
|
762
|
+
|
|
745
763
|
// src/sql-commenter.ts
|
|
746
764
|
var import_klona = require("klona");
|
|
747
765
|
function formatSqlComment(tags) {
|
|
@@ -1025,8 +1043,11 @@ function paginateSingleList(list, { cursor, skip, take }) {
|
|
|
1025
1043
|
const end = take !== null ? start + take : list.length;
|
|
1026
1044
|
return list.slice(start, end);
|
|
1027
1045
|
}
|
|
1028
|
-
function getRecordKey(record, fields) {
|
|
1029
|
-
|
|
1046
|
+
function getRecordKey(record, fields, mappers) {
|
|
1047
|
+
const array = fields.map(
|
|
1048
|
+
(field, index) => mappers?.[index] ? record[field] !== null ? mappers[index](record[field]) : null : record[field]
|
|
1049
|
+
);
|
|
1050
|
+
return JSON.stringify(array);
|
|
1030
1051
|
}
|
|
1031
1052
|
|
|
1032
1053
|
// src/query-plan.ts
|
|
@@ -1063,7 +1084,11 @@ function evaluateArg(arg, scope, generators) {
|
|
|
1063
1084
|
if (found === void 0) {
|
|
1064
1085
|
throw new Error(`Missing value for query variable ${arg.prisma__value.name}`);
|
|
1065
1086
|
}
|
|
1066
|
-
arg
|
|
1087
|
+
if (arg.prisma__value.type === "DateTime" && typeof found === "string") {
|
|
1088
|
+
arg = new Date(found);
|
|
1089
|
+
} else {
|
|
1090
|
+
arg = found;
|
|
1091
|
+
}
|
|
1067
1092
|
} else if (isPrismaValueGenerator(arg)) {
|
|
1068
1093
|
const { name, args } = arg.prisma__value;
|
|
1069
1094
|
const generator = generators[name];
|
|
@@ -1121,7 +1146,10 @@ function renderFragment(fragment, placeholderFormat, ctx) {
|
|
|
1121
1146
|
case "stringChunk":
|
|
1122
1147
|
return fragment.chunk;
|
|
1123
1148
|
case "parameterTuple": {
|
|
1124
|
-
const placeholders = fragment.value.length == 0 ? "NULL" : fragment.value.map(() =>
|
|
1149
|
+
const placeholders = fragment.value.length == 0 ? "NULL" : fragment.value.map(() => {
|
|
1150
|
+
const item = formatPlaceholder(placeholderFormat, ctx.placeholderNumber++);
|
|
1151
|
+
return `${fragment.itemPrefix}${item}${fragment.itemSuffix}`;
|
|
1152
|
+
}).join(fragment.itemSeparator);
|
|
1125
1153
|
return `(${placeholders})`;
|
|
1126
1154
|
}
|
|
1127
1155
|
case "parameterTupleList": {
|
|
@@ -1480,7 +1508,7 @@ function doesSatisfyRule(data, rule) {
|
|
|
1480
1508
|
}
|
|
1481
1509
|
}
|
|
1482
1510
|
function renderMessage(data, error) {
|
|
1483
|
-
switch (error.
|
|
1511
|
+
switch (error.errorIdentifier) {
|
|
1484
1512
|
case "RELATION_VIOLATION":
|
|
1485
1513
|
return `The change you are trying to make would violate the required relation '${error.context.relation}' between the \`${error.context.modelA}\` and \`${error.context.modelB}\` models.`;
|
|
1486
1514
|
case "MISSING_RECORD":
|
|
@@ -1500,7 +1528,7 @@ function renderMessage(data, error) {
|
|
|
1500
1528
|
}
|
|
1501
1529
|
}
|
|
1502
1530
|
function getErrorCode2(error) {
|
|
1503
|
-
switch (error.
|
|
1531
|
+
switch (error.errorIdentifier) {
|
|
1504
1532
|
case "RELATION_VIOLATION":
|
|
1505
1533
|
return "P2014";
|
|
1506
1534
|
case "RECORDS_NOT_CONNECTED":
|
|
@@ -1615,7 +1643,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1615
1643
|
sum += await this.#withQuerySpanAndEvent(
|
|
1616
1644
|
commentedQuery,
|
|
1617
1645
|
context.queryable,
|
|
1618
|
-
() => context.queryable.executeRaw(commentedQuery).catch(
|
|
1646
|
+
() => context.queryable.executeRaw(cloneObject(commentedQuery)).catch(
|
|
1619
1647
|
(err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
|
|
1620
1648
|
)
|
|
1621
1649
|
);
|
|
@@ -1630,7 +1658,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1630
1658
|
const result = await this.#withQuerySpanAndEvent(
|
|
1631
1659
|
commentedQuery,
|
|
1632
1660
|
context.queryable,
|
|
1633
|
-
() => context.queryable.queryRaw(commentedQuery).catch(
|
|
1661
|
+
() => context.queryable.queryRaw(cloneObject(commentedQuery)).catch(
|
|
1634
1662
|
(err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
|
|
1635
1663
|
)
|
|
1636
1664
|
);
|
|
@@ -1682,7 +1710,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1682
1710
|
childRecords: (await this.interpretNode(joinExpr.child, context)).value
|
|
1683
1711
|
}))
|
|
1684
1712
|
);
|
|
1685
|
-
return { value: attachChildrenToParents(parent, children), lastInsertId };
|
|
1713
|
+
return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
|
|
1686
1714
|
}
|
|
1687
1715
|
case "transaction": {
|
|
1688
1716
|
if (!context.transactionManager.enabled) {
|
|
@@ -1729,8 +1757,9 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1729
1757
|
}
|
|
1730
1758
|
case "process": {
|
|
1731
1759
|
const { value, lastInsertId } = await this.interpretNode(node.args.expr, context);
|
|
1732
|
-
|
|
1733
|
-
|
|
1760
|
+
const ops = cloneObject(node.args.operations);
|
|
1761
|
+
evaluateProcessingParameters(ops, context.scope, context.generators);
|
|
1762
|
+
return { value: processRecords(value, ops), lastInsertId };
|
|
1734
1763
|
}
|
|
1735
1764
|
case "initializeRecord": {
|
|
1736
1765
|
const { lastInsertId } = await this.interpretNode(node.args.expr, context);
|
|
@@ -1823,12 +1852,13 @@ function mapField2(value, field) {
|
|
|
1823
1852
|
}
|
|
1824
1853
|
return value;
|
|
1825
1854
|
}
|
|
1826
|
-
function attachChildrenToParents(parentRecords, children) {
|
|
1855
|
+
function attachChildrenToParents(parentRecords, children, canAssumeStrictEquality) {
|
|
1827
1856
|
for (const { joinExpr, childRecords } of children) {
|
|
1828
1857
|
const parentKeys = joinExpr.on.map(([k]) => k);
|
|
1829
1858
|
const childKeys = joinExpr.on.map(([, k]) => k);
|
|
1830
1859
|
const parentMap = {};
|
|
1831
|
-
|
|
1860
|
+
const parentArray = Array.isArray(parentRecords) ? parentRecords : [parentRecords];
|
|
1861
|
+
for (const parent of parentArray) {
|
|
1832
1862
|
const parentRecord = asRecord(parent);
|
|
1833
1863
|
const key = getRecordKey(parentRecord, parentKeys);
|
|
1834
1864
|
if (!parentMap[key]) {
|
|
@@ -1841,11 +1871,12 @@ function attachChildrenToParents(parentRecords, children) {
|
|
|
1841
1871
|
parentRecord[joinExpr.parentField] = [];
|
|
1842
1872
|
}
|
|
1843
1873
|
}
|
|
1874
|
+
const mappers = canAssumeStrictEquality ? void 0 : inferKeyCasts(parentArray, parentKeys);
|
|
1844
1875
|
for (const childRecord of Array.isArray(childRecords) ? childRecords : [childRecords]) {
|
|
1845
1876
|
if (childRecord === null) {
|
|
1846
1877
|
continue;
|
|
1847
1878
|
}
|
|
1848
|
-
const key = getRecordKey(asRecord(childRecord), childKeys);
|
|
1879
|
+
const key = getRecordKey(asRecord(childRecord), childKeys, mappers);
|
|
1849
1880
|
for (const parentRecord of parentMap[key] ?? []) {
|
|
1850
1881
|
if (joinExpr.isRelationUnique) {
|
|
1851
1882
|
parentRecord[joinExpr.parentField] = childRecord;
|
|
@@ -1857,6 +1888,40 @@ function attachChildrenToParents(parentRecords, children) {
|
|
|
1857
1888
|
}
|
|
1858
1889
|
return parentRecords;
|
|
1859
1890
|
}
|
|
1891
|
+
function inferKeyCasts(rows, keys) {
|
|
1892
|
+
function getKeyCast(type) {
|
|
1893
|
+
switch (type) {
|
|
1894
|
+
case "number":
|
|
1895
|
+
return Number;
|
|
1896
|
+
case "string":
|
|
1897
|
+
return String;
|
|
1898
|
+
case "boolean":
|
|
1899
|
+
return Boolean;
|
|
1900
|
+
case "bigint":
|
|
1901
|
+
return BigInt;
|
|
1902
|
+
default:
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
const keyCasts = Array.from({ length: keys.length });
|
|
1907
|
+
let keysFound = 0;
|
|
1908
|
+
for (const parent of rows) {
|
|
1909
|
+
const parentRecord = asRecord(parent);
|
|
1910
|
+
for (const [i, key] of keys.entries()) {
|
|
1911
|
+
if (parentRecord[key] !== null && keyCasts[i] === void 0) {
|
|
1912
|
+
const keyCast = getKeyCast(typeof parentRecord[key]);
|
|
1913
|
+
if (keyCast !== void 0) {
|
|
1914
|
+
keyCasts[i] = keyCast;
|
|
1915
|
+
}
|
|
1916
|
+
keysFound++;
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
if (keysFound === keys.length) {
|
|
1920
|
+
break;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
return keyCasts;
|
|
1924
|
+
}
|
|
1860
1925
|
function evalFieldInitializer(initializer, lastInsertId, scope, generators) {
|
|
1861
1926
|
switch (initializer.type) {
|
|
1862
1927
|
case "value":
|
|
@@ -1916,6 +1981,9 @@ function evaluateProcessingParameters(ops, scope, generators) {
|
|
|
1916
1981
|
evaluateProcessingParameters(nested, scope, generators);
|
|
1917
1982
|
}
|
|
1918
1983
|
}
|
|
1984
|
+
function cloneObject(value) {
|
|
1985
|
+
return (0, import_klona2.klona)(value);
|
|
1986
|
+
}
|
|
1919
1987
|
|
|
1920
1988
|
// src/raw-json-protocol.ts
|
|
1921
1989
|
var import_client_runtime_utils4 = require("@prisma/client-runtime-utils");
|
|
@@ -2072,13 +2140,42 @@ var TransactionManager = class {
|
|
|
2072
2140
|
);
|
|
2073
2141
|
}
|
|
2074
2142
|
async #startTransactionImpl(options) {
|
|
2143
|
+
if (options.newTxId) {
|
|
2144
|
+
return await this.#withActiveTransactionLock(options.newTxId, "start", async (existing) => {
|
|
2145
|
+
if (existing.status !== "running") {
|
|
2146
|
+
throw new TransactionInternalConsistencyError(
|
|
2147
|
+
`Transaction in invalid state ${existing.status} when starting a nested transaction.`
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
if (!existing.transaction) {
|
|
2151
|
+
throw new TransactionInternalConsistencyError(
|
|
2152
|
+
`Transaction missing underlying driver transaction when starting a nested transaction.`
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
existing.depth += 1;
|
|
2156
|
+
const savepointName = this.#nextSavepointName(existing);
|
|
2157
|
+
existing.savepoints.push(savepointName);
|
|
2158
|
+
try {
|
|
2159
|
+
await this.#requiredCreateSavepoint(existing.transaction)(savepointName);
|
|
2160
|
+
} catch (e) {
|
|
2161
|
+
existing.depth -= 1;
|
|
2162
|
+
existing.savepoints.pop();
|
|
2163
|
+
throw e;
|
|
2164
|
+
}
|
|
2165
|
+
return { id: existing.id };
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2075
2168
|
const transaction = {
|
|
2076
2169
|
id: await randomUUID(),
|
|
2077
2170
|
status: "waiting",
|
|
2078
2171
|
timer: void 0,
|
|
2079
2172
|
timeout: options.timeout,
|
|
2080
2173
|
startedAt: Date.now(),
|
|
2081
|
-
transaction: void 0
|
|
2174
|
+
transaction: void 0,
|
|
2175
|
+
operationQueue: Promise.resolve(),
|
|
2176
|
+
depth: 1,
|
|
2177
|
+
savepoints: [],
|
|
2178
|
+
savepointCounter: 0
|
|
2082
2179
|
};
|
|
2083
2180
|
const abortController = new AbortController();
|
|
2084
2181
|
const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
|
|
@@ -2112,14 +2209,49 @@ var TransactionManager = class {
|
|
|
2112
2209
|
}
|
|
2113
2210
|
async commitTransaction(transactionId) {
|
|
2114
2211
|
return await this.tracingHelper.runInChildSpan("commit_transaction", async () => {
|
|
2115
|
-
|
|
2116
|
-
|
|
2212
|
+
await this.#withActiveTransactionLock(transactionId, "commit", async (txw) => {
|
|
2213
|
+
if (txw.depth > 1) {
|
|
2214
|
+
if (!txw.transaction) throw new TransactionNotFoundError();
|
|
2215
|
+
const savepointName = txw.savepoints.at(-1);
|
|
2216
|
+
if (!savepointName) {
|
|
2217
|
+
throw new TransactionInternalConsistencyError(
|
|
2218
|
+
`Missing savepoint for nested commit. Depth: ${txw.depth}, transactionId: ${txw.id}`
|
|
2219
|
+
);
|
|
2220
|
+
}
|
|
2221
|
+
try {
|
|
2222
|
+
await this.#releaseSavepoint(txw.transaction, savepointName);
|
|
2223
|
+
} finally {
|
|
2224
|
+
txw.savepoints.pop();
|
|
2225
|
+
txw.depth -= 1;
|
|
2226
|
+
}
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
await this.#closeTransaction(txw, "committed");
|
|
2230
|
+
});
|
|
2117
2231
|
});
|
|
2118
2232
|
}
|
|
2119
2233
|
async rollbackTransaction(transactionId) {
|
|
2120
2234
|
return await this.tracingHelper.runInChildSpan("rollback_transaction", async () => {
|
|
2121
|
-
|
|
2122
|
-
|
|
2235
|
+
await this.#withActiveTransactionLock(transactionId, "rollback", async (txw) => {
|
|
2236
|
+
if (txw.depth > 1) {
|
|
2237
|
+
if (!txw.transaction) throw new TransactionNotFoundError();
|
|
2238
|
+
const savepointName = txw.savepoints.at(-1);
|
|
2239
|
+
if (!savepointName) {
|
|
2240
|
+
throw new TransactionInternalConsistencyError(
|
|
2241
|
+
`Missing savepoint for nested rollback. Depth: ${txw.depth}, transactionId: ${txw.id}`
|
|
2242
|
+
);
|
|
2243
|
+
}
|
|
2244
|
+
try {
|
|
2245
|
+
await this.#requiredRollbackToSavepoint(txw.transaction)(savepointName);
|
|
2246
|
+
await this.#releaseSavepoint(txw.transaction, savepointName);
|
|
2247
|
+
} finally {
|
|
2248
|
+
txw.savepoints.pop();
|
|
2249
|
+
txw.depth -= 1;
|
|
2250
|
+
}
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
await this.#closeTransaction(txw, "rolled_back");
|
|
2254
|
+
});
|
|
2123
2255
|
});
|
|
2124
2256
|
}
|
|
2125
2257
|
async getTransaction(txInfo, operation) {
|
|
@@ -2163,22 +2295,90 @@ var TransactionManager = class {
|
|
|
2163
2295
|
return transaction;
|
|
2164
2296
|
}
|
|
2165
2297
|
async cancelAllTransactions() {
|
|
2166
|
-
await Promise.allSettled(
|
|
2298
|
+
await Promise.allSettled(
|
|
2299
|
+
[...this.transactions.values()].map(
|
|
2300
|
+
(tx) => this.#runSerialized(tx, async () => {
|
|
2301
|
+
const current = this.transactions.get(tx.id);
|
|
2302
|
+
if (current) {
|
|
2303
|
+
await this.#closeTransaction(current, "rolled_back");
|
|
2304
|
+
}
|
|
2305
|
+
})
|
|
2306
|
+
)
|
|
2307
|
+
);
|
|
2308
|
+
}
|
|
2309
|
+
#nextSavepointName(transaction) {
|
|
2310
|
+
return `prisma_sp_${transaction.savepointCounter++}`;
|
|
2311
|
+
}
|
|
2312
|
+
#requiredCreateSavepoint(transaction) {
|
|
2313
|
+
if (transaction.createSavepoint) {
|
|
2314
|
+
return transaction.createSavepoint.bind(transaction);
|
|
2315
|
+
}
|
|
2316
|
+
throw new TransactionManagerError(
|
|
2317
|
+
`Nested transactions are not supported by adapter "${transaction.adapterName}" (${transaction.provider}): createSavepoint is not implemented.`
|
|
2318
|
+
);
|
|
2319
|
+
}
|
|
2320
|
+
#requiredRollbackToSavepoint(transaction) {
|
|
2321
|
+
if (transaction.rollbackToSavepoint) {
|
|
2322
|
+
return transaction.rollbackToSavepoint.bind(transaction);
|
|
2323
|
+
}
|
|
2324
|
+
throw new TransactionManagerError(
|
|
2325
|
+
`Nested transactions are not supported by adapter "${transaction.adapterName}" (${transaction.provider}): rollbackToSavepoint is not implemented.`
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2328
|
+
async #releaseSavepoint(transaction, name) {
|
|
2329
|
+
if (transaction.releaseSavepoint) {
|
|
2330
|
+
await transaction.releaseSavepoint(name);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
#debugTransactionAlreadyClosedOnTimeout(transactionId) {
|
|
2334
|
+
debug("Transaction already committed or rolled back when timeout happened.", transactionId);
|
|
2167
2335
|
}
|
|
2168
2336
|
#startTransactionTimeout(transactionId, timeout) {
|
|
2169
2337
|
const timeoutStartedAt = Date.now();
|
|
2170
2338
|
const timer = createTimeoutIfDefined(async () => {
|
|
2171
2339
|
debug("Transaction timed out.", { transactionId, timeoutStartedAt, timeout });
|
|
2172
2340
|
const tx = this.transactions.get(transactionId);
|
|
2173
|
-
if (tx
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2341
|
+
if (!tx) {
|
|
2342
|
+
this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
await this.#runSerialized(tx, async () => {
|
|
2346
|
+
const current = this.transactions.get(transactionId);
|
|
2347
|
+
if (current && ["running", "waiting"].includes(current.status)) {
|
|
2348
|
+
await this.#closeTransaction(current, "timed_out");
|
|
2349
|
+
} else {
|
|
2350
|
+
this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
|
|
2351
|
+
}
|
|
2352
|
+
});
|
|
2178
2353
|
}, timeout);
|
|
2179
2354
|
timer?.unref?.();
|
|
2180
2355
|
return timer;
|
|
2181
2356
|
}
|
|
2357
|
+
// Any operation that mutates or closes a transaction must run through this lock so
|
|
2358
|
+
// status/savepoint/depth checks and updates happen against a stable view of state.
|
|
2359
|
+
async #withActiveTransactionLock(transactionId, operation, callback) {
|
|
2360
|
+
const tx = this.#getActiveOrClosingTransaction(transactionId, operation);
|
|
2361
|
+
return await this.#runSerialized(tx, async () => {
|
|
2362
|
+
const current = this.#getActiveOrClosingTransaction(transactionId, operation);
|
|
2363
|
+
return await callback(current);
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
// Serializes operations per transaction id to prevent interleaving across awaits.
|
|
2367
|
+
// This avoids races where one operation mutates savepoint/depth state while another
|
|
2368
|
+
// operation is suspended, which could otherwise corrupt cleanup logic.
|
|
2369
|
+
async #runSerialized(tx, callback) {
|
|
2370
|
+
const previousOperation = tx.operationQueue;
|
|
2371
|
+
let releaseOperationLock;
|
|
2372
|
+
tx.operationQueue = new Promise((resolve) => {
|
|
2373
|
+
releaseOperationLock = resolve;
|
|
2374
|
+
});
|
|
2375
|
+
await previousOperation;
|
|
2376
|
+
try {
|
|
2377
|
+
return await callback();
|
|
2378
|
+
} finally {
|
|
2379
|
+
releaseOperationLock();
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2182
2382
|
async #closeTransaction(tx, status) {
|
|
2183
2383
|
const createClosingPromise = async () => {
|
|
2184
2384
|
debug("Closing transaction.", { transactionId: tx.id, status });
|
|
@@ -2266,7 +2466,7 @@ function createTimeoutIfDefined(cb, ms) {
|
|
|
2266
2466
|
UserFacingError,
|
|
2267
2467
|
applySqlCommenters,
|
|
2268
2468
|
convertCompactedRows,
|
|
2269
|
-
|
|
2469
|
+
deserializeJsonObject,
|
|
2270
2470
|
doKeysMatch,
|
|
2271
2471
|
isDeepStrictEqual,
|
|
2272
2472
|
isPrismaValueGenerator,
|