@prisma/client-engine-runtime 7.5.0-dev.9 → 7.5.0
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 +17 -8
- package/dist/index.d.ts +17 -8
- package/dist/index.js +210 -25
- package/dist/index.mjs +210 -25
- 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/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/events.d.ts
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -80,6 +80,10 @@ export declare type DecimalTaggedValue = {
|
|
|
80
80
|
value: string;
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
+
declare type DeepReadonly<T> = T extends undefined | null | boolean | string | number | symbol | Function | Date ? T : T extends Array<infer U> ? ReadonlyArray<DeepReadonly<U>> : unknown extends T ? unknown : {
|
|
84
|
+
readonly [K in keyof T]: DeepReadonly<T[K]>;
|
|
85
|
+
};
|
|
86
|
+
|
|
83
87
|
export declare function deserializeJsonObject(result: unknown): unknown;
|
|
84
88
|
|
|
85
89
|
/**
|
|
@@ -155,6 +159,9 @@ export declare type Fragment = {
|
|
|
155
159
|
type: 'parameter';
|
|
156
160
|
} | {
|
|
157
161
|
type: 'parameterTuple';
|
|
162
|
+
itemPrefix: string;
|
|
163
|
+
itemSeparator: string;
|
|
164
|
+
itemSuffix: string;
|
|
158
165
|
} | {
|
|
159
166
|
type: 'parameterTupleList';
|
|
160
167
|
itemPrefix: string;
|
|
@@ -243,7 +250,7 @@ export declare type PrismaValuePlaceholder = {
|
|
|
243
250
|
export declare type QueryEvent = {
|
|
244
251
|
timestamp: Date;
|
|
245
252
|
query: string;
|
|
246
|
-
params: unknown[];
|
|
253
|
+
params: readonly unknown[];
|
|
247
254
|
duration: number;
|
|
248
255
|
};
|
|
249
256
|
|
|
@@ -256,7 +263,7 @@ export declare class QueryInterpreter {
|
|
|
256
263
|
provider?: SchemaProvider;
|
|
257
264
|
connectionInfo?: ConnectionInfo;
|
|
258
265
|
}): QueryInterpreter;
|
|
259
|
-
run(queryPlan: QueryPlanNode
|
|
266
|
+
run(queryPlan: DeepReadonly<QueryPlanNode>, options: QueryRuntimeOptions): Promise<unknown>;
|
|
260
267
|
private interpretNode;
|
|
261
268
|
}
|
|
262
269
|
|
|
@@ -348,6 +355,7 @@ export declare type QueryPlanNode = {
|
|
|
348
355
|
args: {
|
|
349
356
|
parent: QueryPlanNode;
|
|
350
357
|
children: JoinExpression[];
|
|
358
|
+
canAssumeStrictEquality: boolean;
|
|
351
359
|
};
|
|
352
360
|
} | {
|
|
353
361
|
type: 'mapField';
|
|
@@ -497,6 +505,7 @@ export declare type TransactionOptions = {
|
|
|
497
505
|
maxWait?: number;
|
|
498
506
|
timeout?: number;
|
|
499
507
|
isolationLevel?: IsolationLevel;
|
|
508
|
+
newTxId?: string;
|
|
500
509
|
};
|
|
501
510
|
|
|
502
511
|
export declare class UserFacingError extends Error {
|
|
@@ -516,14 +525,14 @@ export declare class UserFacingError extends Error {
|
|
|
516
525
|
}
|
|
517
526
|
|
|
518
527
|
export declare type ValidationError = {
|
|
519
|
-
|
|
528
|
+
errorIdentifier: 'RELATION_VIOLATION';
|
|
520
529
|
context: {
|
|
521
530
|
relation: string;
|
|
522
531
|
modelA: string;
|
|
523
532
|
modelB: string;
|
|
524
533
|
};
|
|
525
534
|
} | {
|
|
526
|
-
|
|
535
|
+
errorIdentifier: 'MISSING_RELATED_RECORD';
|
|
527
536
|
context: {
|
|
528
537
|
model: string;
|
|
529
538
|
relation: string;
|
|
@@ -532,24 +541,24 @@ export declare type ValidationError = {
|
|
|
532
541
|
neededFor?: string;
|
|
533
542
|
};
|
|
534
543
|
} | {
|
|
535
|
-
|
|
544
|
+
errorIdentifier: 'MISSING_RECORD';
|
|
536
545
|
context: {
|
|
537
546
|
operation: string;
|
|
538
547
|
};
|
|
539
548
|
} | {
|
|
540
|
-
|
|
549
|
+
errorIdentifier: 'INCOMPLETE_CONNECT_INPUT';
|
|
541
550
|
context: {
|
|
542
551
|
expectedRows: number;
|
|
543
552
|
};
|
|
544
553
|
} | {
|
|
545
|
-
|
|
554
|
+
errorIdentifier: 'INCOMPLETE_CONNECT_OUTPUT';
|
|
546
555
|
context: {
|
|
547
556
|
expectedRows: number;
|
|
548
557
|
relation: string;
|
|
549
558
|
relationType: string;
|
|
550
559
|
};
|
|
551
560
|
} | {
|
|
552
|
-
|
|
561
|
+
errorIdentifier: 'RECORDS_NOT_CONNECTED';
|
|
553
562
|
context: {
|
|
554
563
|
relation: string;
|
|
555
564
|
parent: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -80,6 +80,10 @@ export declare type DecimalTaggedValue = {
|
|
|
80
80
|
value: string;
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
+
declare type DeepReadonly<T> = T extends undefined | null | boolean | string | number | symbol | Function | Date ? T : T extends Array<infer U> ? ReadonlyArray<DeepReadonly<U>> : unknown extends T ? unknown : {
|
|
84
|
+
readonly [K in keyof T]: DeepReadonly<T[K]>;
|
|
85
|
+
};
|
|
86
|
+
|
|
83
87
|
export declare function deserializeJsonObject(result: unknown): unknown;
|
|
84
88
|
|
|
85
89
|
/**
|
|
@@ -155,6 +159,9 @@ export declare type Fragment = {
|
|
|
155
159
|
type: 'parameter';
|
|
156
160
|
} | {
|
|
157
161
|
type: 'parameterTuple';
|
|
162
|
+
itemPrefix: string;
|
|
163
|
+
itemSeparator: string;
|
|
164
|
+
itemSuffix: string;
|
|
158
165
|
} | {
|
|
159
166
|
type: 'parameterTupleList';
|
|
160
167
|
itemPrefix: string;
|
|
@@ -243,7 +250,7 @@ export declare type PrismaValuePlaceholder = {
|
|
|
243
250
|
export declare type QueryEvent = {
|
|
244
251
|
timestamp: Date;
|
|
245
252
|
query: string;
|
|
246
|
-
params: unknown[];
|
|
253
|
+
params: readonly unknown[];
|
|
247
254
|
duration: number;
|
|
248
255
|
};
|
|
249
256
|
|
|
@@ -256,7 +263,7 @@ export declare class QueryInterpreter {
|
|
|
256
263
|
provider?: SchemaProvider;
|
|
257
264
|
connectionInfo?: ConnectionInfo;
|
|
258
265
|
}): QueryInterpreter;
|
|
259
|
-
run(queryPlan: QueryPlanNode
|
|
266
|
+
run(queryPlan: DeepReadonly<QueryPlanNode>, options: QueryRuntimeOptions): Promise<unknown>;
|
|
260
267
|
private interpretNode;
|
|
261
268
|
}
|
|
262
269
|
|
|
@@ -348,6 +355,7 @@ export declare type QueryPlanNode = {
|
|
|
348
355
|
args: {
|
|
349
356
|
parent: QueryPlanNode;
|
|
350
357
|
children: JoinExpression[];
|
|
358
|
+
canAssumeStrictEquality: boolean;
|
|
351
359
|
};
|
|
352
360
|
} | {
|
|
353
361
|
type: 'mapField';
|
|
@@ -497,6 +505,7 @@ export declare type TransactionOptions = {
|
|
|
497
505
|
maxWait?: number;
|
|
498
506
|
timeout?: number;
|
|
499
507
|
isolationLevel?: IsolationLevel;
|
|
508
|
+
newTxId?: string;
|
|
500
509
|
};
|
|
501
510
|
|
|
502
511
|
export declare class UserFacingError extends Error {
|
|
@@ -516,14 +525,14 @@ export declare class UserFacingError extends Error {
|
|
|
516
525
|
}
|
|
517
526
|
|
|
518
527
|
export declare type ValidationError = {
|
|
519
|
-
|
|
528
|
+
errorIdentifier: 'RELATION_VIOLATION';
|
|
520
529
|
context: {
|
|
521
530
|
relation: string;
|
|
522
531
|
modelA: string;
|
|
523
532
|
modelB: string;
|
|
524
533
|
};
|
|
525
534
|
} | {
|
|
526
|
-
|
|
535
|
+
errorIdentifier: 'MISSING_RELATED_RECORD';
|
|
527
536
|
context: {
|
|
528
537
|
model: string;
|
|
529
538
|
relation: string;
|
|
@@ -532,24 +541,24 @@ export declare type ValidationError = {
|
|
|
532
541
|
neededFor?: string;
|
|
533
542
|
};
|
|
534
543
|
} | {
|
|
535
|
-
|
|
544
|
+
errorIdentifier: 'MISSING_RECORD';
|
|
536
545
|
context: {
|
|
537
546
|
operation: string;
|
|
538
547
|
};
|
|
539
548
|
} | {
|
|
540
|
-
|
|
549
|
+
errorIdentifier: 'INCOMPLETE_CONNECT_INPUT';
|
|
541
550
|
context: {
|
|
542
551
|
expectedRows: number;
|
|
543
552
|
};
|
|
544
553
|
} | {
|
|
545
|
-
|
|
554
|
+
errorIdentifier: 'INCOMPLETE_CONNECT_OUTPUT';
|
|
546
555
|
context: {
|
|
547
556
|
expectedRows: number;
|
|
548
557
|
relation: string;
|
|
549
558
|
relationType: string;
|
|
550
559
|
};
|
|
551
560
|
} | {
|
|
552
|
-
|
|
561
|
+
errorIdentifier: 'RECORDS_NOT_CONNECTED';
|
|
553
562
|
context: {
|
|
554
563
|
relation: string;
|
|
555
564
|
parent: string;
|
package/dist/index.js
CHANGED
|
@@ -757,6 +757,9 @@ function normalizeDateTime(dt) {
|
|
|
757
757
|
return dtWithTz;
|
|
758
758
|
}
|
|
759
759
|
|
|
760
|
+
// src/interpreter/query-interpreter.ts
|
|
761
|
+
var import_klona2 = require("klona");
|
|
762
|
+
|
|
760
763
|
// src/sql-commenter.ts
|
|
761
764
|
var import_klona = require("klona");
|
|
762
765
|
function formatSqlComment(tags) {
|
|
@@ -1040,8 +1043,11 @@ function paginateSingleList(list, { cursor, skip, take }) {
|
|
|
1040
1043
|
const end = take !== null ? start + take : list.length;
|
|
1041
1044
|
return list.slice(start, end);
|
|
1042
1045
|
}
|
|
1043
|
-
function getRecordKey(record, fields) {
|
|
1044
|
-
|
|
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);
|
|
1045
1051
|
}
|
|
1046
1052
|
|
|
1047
1053
|
// src/query-plan.ts
|
|
@@ -1078,7 +1084,11 @@ function evaluateArg(arg, scope, generators) {
|
|
|
1078
1084
|
if (found === void 0) {
|
|
1079
1085
|
throw new Error(`Missing value for query variable ${arg.prisma__value.name}`);
|
|
1080
1086
|
}
|
|
1081
|
-
arg
|
|
1087
|
+
if (arg.prisma__value.type === "DateTime" && typeof found === "string") {
|
|
1088
|
+
arg = new Date(found);
|
|
1089
|
+
} else {
|
|
1090
|
+
arg = found;
|
|
1091
|
+
}
|
|
1082
1092
|
} else if (isPrismaValueGenerator(arg)) {
|
|
1083
1093
|
const { name, args } = arg.prisma__value;
|
|
1084
1094
|
const generator = generators[name];
|
|
@@ -1136,7 +1146,10 @@ function renderFragment(fragment, placeholderFormat, ctx) {
|
|
|
1136
1146
|
case "stringChunk":
|
|
1137
1147
|
return fragment.chunk;
|
|
1138
1148
|
case "parameterTuple": {
|
|
1139
|
-
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);
|
|
1140
1153
|
return `(${placeholders})`;
|
|
1141
1154
|
}
|
|
1142
1155
|
case "parameterTupleList": {
|
|
@@ -1495,7 +1508,7 @@ function doesSatisfyRule(data, rule) {
|
|
|
1495
1508
|
}
|
|
1496
1509
|
}
|
|
1497
1510
|
function renderMessage(data, error) {
|
|
1498
|
-
switch (error.
|
|
1511
|
+
switch (error.errorIdentifier) {
|
|
1499
1512
|
case "RELATION_VIOLATION":
|
|
1500
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.`;
|
|
1501
1514
|
case "MISSING_RECORD":
|
|
@@ -1515,7 +1528,7 @@ function renderMessage(data, error) {
|
|
|
1515
1528
|
}
|
|
1516
1529
|
}
|
|
1517
1530
|
function getErrorCode2(error) {
|
|
1518
|
-
switch (error.
|
|
1531
|
+
switch (error.errorIdentifier) {
|
|
1519
1532
|
case "RELATION_VIOLATION":
|
|
1520
1533
|
return "P2014";
|
|
1521
1534
|
case "RECORDS_NOT_CONNECTED":
|
|
@@ -1630,7 +1643,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1630
1643
|
sum += await this.#withQuerySpanAndEvent(
|
|
1631
1644
|
commentedQuery,
|
|
1632
1645
|
context.queryable,
|
|
1633
|
-
() => context.queryable.executeRaw(commentedQuery).catch(
|
|
1646
|
+
() => context.queryable.executeRaw(cloneObject(commentedQuery)).catch(
|
|
1634
1647
|
(err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
|
|
1635
1648
|
)
|
|
1636
1649
|
);
|
|
@@ -1645,7 +1658,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1645
1658
|
const result = await this.#withQuerySpanAndEvent(
|
|
1646
1659
|
commentedQuery,
|
|
1647
1660
|
context.queryable,
|
|
1648
|
-
() => context.queryable.queryRaw(commentedQuery).catch(
|
|
1661
|
+
() => context.queryable.queryRaw(cloneObject(commentedQuery)).catch(
|
|
1649
1662
|
(err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
|
|
1650
1663
|
)
|
|
1651
1664
|
);
|
|
@@ -1697,7 +1710,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1697
1710
|
childRecords: (await this.interpretNode(joinExpr.child, context)).value
|
|
1698
1711
|
}))
|
|
1699
1712
|
);
|
|
1700
|
-
return { value: attachChildrenToParents(parent, children), lastInsertId };
|
|
1713
|
+
return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
|
|
1701
1714
|
}
|
|
1702
1715
|
case "transaction": {
|
|
1703
1716
|
if (!context.transactionManager.enabled) {
|
|
@@ -1744,8 +1757,9 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1744
1757
|
}
|
|
1745
1758
|
case "process": {
|
|
1746
1759
|
const { value, lastInsertId } = await this.interpretNode(node.args.expr, context);
|
|
1747
|
-
|
|
1748
|
-
|
|
1760
|
+
const ops = cloneObject(node.args.operations);
|
|
1761
|
+
evaluateProcessingParameters(ops, context.scope, context.generators);
|
|
1762
|
+
return { value: processRecords(value, ops), lastInsertId };
|
|
1749
1763
|
}
|
|
1750
1764
|
case "initializeRecord": {
|
|
1751
1765
|
const { lastInsertId } = await this.interpretNode(node.args.expr, context);
|
|
@@ -1838,12 +1852,13 @@ function mapField2(value, field) {
|
|
|
1838
1852
|
}
|
|
1839
1853
|
return value;
|
|
1840
1854
|
}
|
|
1841
|
-
function attachChildrenToParents(parentRecords, children) {
|
|
1855
|
+
function attachChildrenToParents(parentRecords, children, canAssumeStrictEquality) {
|
|
1842
1856
|
for (const { joinExpr, childRecords } of children) {
|
|
1843
1857
|
const parentKeys = joinExpr.on.map(([k]) => k);
|
|
1844
1858
|
const childKeys = joinExpr.on.map(([, k]) => k);
|
|
1845
1859
|
const parentMap = {};
|
|
1846
|
-
|
|
1860
|
+
const parentArray = Array.isArray(parentRecords) ? parentRecords : [parentRecords];
|
|
1861
|
+
for (const parent of parentArray) {
|
|
1847
1862
|
const parentRecord = asRecord(parent);
|
|
1848
1863
|
const key = getRecordKey(parentRecord, parentKeys);
|
|
1849
1864
|
if (!parentMap[key]) {
|
|
@@ -1856,11 +1871,12 @@ function attachChildrenToParents(parentRecords, children) {
|
|
|
1856
1871
|
parentRecord[joinExpr.parentField] = [];
|
|
1857
1872
|
}
|
|
1858
1873
|
}
|
|
1874
|
+
const mappers = canAssumeStrictEquality ? void 0 : inferKeyCasts(parentArray, parentKeys);
|
|
1859
1875
|
for (const childRecord of Array.isArray(childRecords) ? childRecords : [childRecords]) {
|
|
1860
1876
|
if (childRecord === null) {
|
|
1861
1877
|
continue;
|
|
1862
1878
|
}
|
|
1863
|
-
const key = getRecordKey(asRecord(childRecord), childKeys);
|
|
1879
|
+
const key = getRecordKey(asRecord(childRecord), childKeys, mappers);
|
|
1864
1880
|
for (const parentRecord of parentMap[key] ?? []) {
|
|
1865
1881
|
if (joinExpr.isRelationUnique) {
|
|
1866
1882
|
parentRecord[joinExpr.parentField] = childRecord;
|
|
@@ -1872,6 +1888,40 @@ function attachChildrenToParents(parentRecords, children) {
|
|
|
1872
1888
|
}
|
|
1873
1889
|
return parentRecords;
|
|
1874
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
|
+
}
|
|
1875
1925
|
function evalFieldInitializer(initializer, lastInsertId, scope, generators) {
|
|
1876
1926
|
switch (initializer.type) {
|
|
1877
1927
|
case "value":
|
|
@@ -1931,6 +1981,9 @@ function evaluateProcessingParameters(ops, scope, generators) {
|
|
|
1931
1981
|
evaluateProcessingParameters(nested, scope, generators);
|
|
1932
1982
|
}
|
|
1933
1983
|
}
|
|
1984
|
+
function cloneObject(value) {
|
|
1985
|
+
return (0, import_klona2.klona)(value);
|
|
1986
|
+
}
|
|
1934
1987
|
|
|
1935
1988
|
// src/raw-json-protocol.ts
|
|
1936
1989
|
var import_client_runtime_utils4 = require("@prisma/client-runtime-utils");
|
|
@@ -2087,13 +2140,42 @@ var TransactionManager = class {
|
|
|
2087
2140
|
);
|
|
2088
2141
|
}
|
|
2089
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
|
+
}
|
|
2090
2168
|
const transaction = {
|
|
2091
2169
|
id: await randomUUID(),
|
|
2092
2170
|
status: "waiting",
|
|
2093
2171
|
timer: void 0,
|
|
2094
2172
|
timeout: options.timeout,
|
|
2095
2173
|
startedAt: Date.now(),
|
|
2096
|
-
transaction: void 0
|
|
2174
|
+
transaction: void 0,
|
|
2175
|
+
operationQueue: Promise.resolve(),
|
|
2176
|
+
depth: 1,
|
|
2177
|
+
savepoints: [],
|
|
2178
|
+
savepointCounter: 0
|
|
2097
2179
|
};
|
|
2098
2180
|
const abortController = new AbortController();
|
|
2099
2181
|
const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
|
|
@@ -2127,14 +2209,49 @@ var TransactionManager = class {
|
|
|
2127
2209
|
}
|
|
2128
2210
|
async commitTransaction(transactionId) {
|
|
2129
2211
|
return await this.tracingHelper.runInChildSpan("commit_transaction", async () => {
|
|
2130
|
-
|
|
2131
|
-
|
|
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
|
+
});
|
|
2132
2231
|
});
|
|
2133
2232
|
}
|
|
2134
2233
|
async rollbackTransaction(transactionId) {
|
|
2135
2234
|
return await this.tracingHelper.runInChildSpan("rollback_transaction", async () => {
|
|
2136
|
-
|
|
2137
|
-
|
|
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
|
+
});
|
|
2138
2255
|
});
|
|
2139
2256
|
}
|
|
2140
2257
|
async getTransaction(txInfo, operation) {
|
|
@@ -2178,22 +2295,90 @@ var TransactionManager = class {
|
|
|
2178
2295
|
return transaction;
|
|
2179
2296
|
}
|
|
2180
2297
|
async cancelAllTransactions() {
|
|
2181
|
-
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);
|
|
2182
2335
|
}
|
|
2183
2336
|
#startTransactionTimeout(transactionId, timeout) {
|
|
2184
2337
|
const timeoutStartedAt = Date.now();
|
|
2185
2338
|
const timer = createTimeoutIfDefined(async () => {
|
|
2186
2339
|
debug("Transaction timed out.", { transactionId, timeoutStartedAt, timeout });
|
|
2187
2340
|
const tx = this.transactions.get(transactionId);
|
|
2188
|
-
if (tx
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
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
|
+
});
|
|
2193
2353
|
}, timeout);
|
|
2194
2354
|
timer?.unref?.();
|
|
2195
2355
|
return timer;
|
|
2196
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
|
+
}
|
|
2197
2382
|
async #closeTransaction(tx, status) {
|
|
2198
2383
|
const createClosingPromise = async () => {
|
|
2199
2384
|
debug("Closing transaction.", { transactionId: tx.id, status });
|
package/dist/index.mjs
CHANGED
|
@@ -706,6 +706,9 @@ function normalizeDateTime(dt) {
|
|
|
706
706
|
return dtWithTz;
|
|
707
707
|
}
|
|
708
708
|
|
|
709
|
+
// src/interpreter/query-interpreter.ts
|
|
710
|
+
import { klona as klona2 } from "klona";
|
|
711
|
+
|
|
709
712
|
// src/sql-commenter.ts
|
|
710
713
|
import { klona } from "klona";
|
|
711
714
|
function formatSqlComment(tags) {
|
|
@@ -989,8 +992,11 @@ function paginateSingleList(list, { cursor, skip, take }) {
|
|
|
989
992
|
const end = take !== null ? start + take : list.length;
|
|
990
993
|
return list.slice(start, end);
|
|
991
994
|
}
|
|
992
|
-
function getRecordKey(record, fields) {
|
|
993
|
-
|
|
995
|
+
function getRecordKey(record, fields, mappers) {
|
|
996
|
+
const array = fields.map(
|
|
997
|
+
(field, index) => mappers?.[index] ? record[field] !== null ? mappers[index](record[field]) : null : record[field]
|
|
998
|
+
);
|
|
999
|
+
return JSON.stringify(array);
|
|
994
1000
|
}
|
|
995
1001
|
|
|
996
1002
|
// src/query-plan.ts
|
|
@@ -1027,7 +1033,11 @@ function evaluateArg(arg, scope, generators) {
|
|
|
1027
1033
|
if (found === void 0) {
|
|
1028
1034
|
throw new Error(`Missing value for query variable ${arg.prisma__value.name}`);
|
|
1029
1035
|
}
|
|
1030
|
-
arg
|
|
1036
|
+
if (arg.prisma__value.type === "DateTime" && typeof found === "string") {
|
|
1037
|
+
arg = new Date(found);
|
|
1038
|
+
} else {
|
|
1039
|
+
arg = found;
|
|
1040
|
+
}
|
|
1031
1041
|
} else if (isPrismaValueGenerator(arg)) {
|
|
1032
1042
|
const { name, args } = arg.prisma__value;
|
|
1033
1043
|
const generator = generators[name];
|
|
@@ -1085,7 +1095,10 @@ function renderFragment(fragment, placeholderFormat, ctx) {
|
|
|
1085
1095
|
case "stringChunk":
|
|
1086
1096
|
return fragment.chunk;
|
|
1087
1097
|
case "parameterTuple": {
|
|
1088
|
-
const placeholders = fragment.value.length == 0 ? "NULL" : fragment.value.map(() =>
|
|
1098
|
+
const placeholders = fragment.value.length == 0 ? "NULL" : fragment.value.map(() => {
|
|
1099
|
+
const item = formatPlaceholder(placeholderFormat, ctx.placeholderNumber++);
|
|
1100
|
+
return `${fragment.itemPrefix}${item}${fragment.itemSuffix}`;
|
|
1101
|
+
}).join(fragment.itemSeparator);
|
|
1089
1102
|
return `(${placeholders})`;
|
|
1090
1103
|
}
|
|
1091
1104
|
case "parameterTupleList": {
|
|
@@ -1444,7 +1457,7 @@ function doesSatisfyRule(data, rule) {
|
|
|
1444
1457
|
}
|
|
1445
1458
|
}
|
|
1446
1459
|
function renderMessage(data, error) {
|
|
1447
|
-
switch (error.
|
|
1460
|
+
switch (error.errorIdentifier) {
|
|
1448
1461
|
case "RELATION_VIOLATION":
|
|
1449
1462
|
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.`;
|
|
1450
1463
|
case "MISSING_RECORD":
|
|
@@ -1464,7 +1477,7 @@ function renderMessage(data, error) {
|
|
|
1464
1477
|
}
|
|
1465
1478
|
}
|
|
1466
1479
|
function getErrorCode2(error) {
|
|
1467
|
-
switch (error.
|
|
1480
|
+
switch (error.errorIdentifier) {
|
|
1468
1481
|
case "RELATION_VIOLATION":
|
|
1469
1482
|
return "P2014";
|
|
1470
1483
|
case "RECORDS_NOT_CONNECTED":
|
|
@@ -1579,7 +1592,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1579
1592
|
sum += await this.#withQuerySpanAndEvent(
|
|
1580
1593
|
commentedQuery,
|
|
1581
1594
|
context.queryable,
|
|
1582
|
-
() => context.queryable.executeRaw(commentedQuery).catch(
|
|
1595
|
+
() => context.queryable.executeRaw(cloneObject(commentedQuery)).catch(
|
|
1583
1596
|
(err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
|
|
1584
1597
|
)
|
|
1585
1598
|
);
|
|
@@ -1594,7 +1607,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1594
1607
|
const result = await this.#withQuerySpanAndEvent(
|
|
1595
1608
|
commentedQuery,
|
|
1596
1609
|
context.queryable,
|
|
1597
|
-
() => context.queryable.queryRaw(commentedQuery).catch(
|
|
1610
|
+
() => context.queryable.queryRaw(cloneObject(commentedQuery)).catch(
|
|
1598
1611
|
(err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
|
|
1599
1612
|
)
|
|
1600
1613
|
);
|
|
@@ -1646,7 +1659,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1646
1659
|
childRecords: (await this.interpretNode(joinExpr.child, context)).value
|
|
1647
1660
|
}))
|
|
1648
1661
|
);
|
|
1649
|
-
return { value: attachChildrenToParents(parent, children), lastInsertId };
|
|
1662
|
+
return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
|
|
1650
1663
|
}
|
|
1651
1664
|
case "transaction": {
|
|
1652
1665
|
if (!context.transactionManager.enabled) {
|
|
@@ -1693,8 +1706,9 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1693
1706
|
}
|
|
1694
1707
|
case "process": {
|
|
1695
1708
|
const { value, lastInsertId } = await this.interpretNode(node.args.expr, context);
|
|
1696
|
-
|
|
1697
|
-
|
|
1709
|
+
const ops = cloneObject(node.args.operations);
|
|
1710
|
+
evaluateProcessingParameters(ops, context.scope, context.generators);
|
|
1711
|
+
return { value: processRecords(value, ops), lastInsertId };
|
|
1698
1712
|
}
|
|
1699
1713
|
case "initializeRecord": {
|
|
1700
1714
|
const { lastInsertId } = await this.interpretNode(node.args.expr, context);
|
|
@@ -1787,12 +1801,13 @@ function mapField2(value, field) {
|
|
|
1787
1801
|
}
|
|
1788
1802
|
return value;
|
|
1789
1803
|
}
|
|
1790
|
-
function attachChildrenToParents(parentRecords, children) {
|
|
1804
|
+
function attachChildrenToParents(parentRecords, children, canAssumeStrictEquality) {
|
|
1791
1805
|
for (const { joinExpr, childRecords } of children) {
|
|
1792
1806
|
const parentKeys = joinExpr.on.map(([k]) => k);
|
|
1793
1807
|
const childKeys = joinExpr.on.map(([, k]) => k);
|
|
1794
1808
|
const parentMap = {};
|
|
1795
|
-
|
|
1809
|
+
const parentArray = Array.isArray(parentRecords) ? parentRecords : [parentRecords];
|
|
1810
|
+
for (const parent of parentArray) {
|
|
1796
1811
|
const parentRecord = asRecord(parent);
|
|
1797
1812
|
const key = getRecordKey(parentRecord, parentKeys);
|
|
1798
1813
|
if (!parentMap[key]) {
|
|
@@ -1805,11 +1820,12 @@ function attachChildrenToParents(parentRecords, children) {
|
|
|
1805
1820
|
parentRecord[joinExpr.parentField] = [];
|
|
1806
1821
|
}
|
|
1807
1822
|
}
|
|
1823
|
+
const mappers = canAssumeStrictEquality ? void 0 : inferKeyCasts(parentArray, parentKeys);
|
|
1808
1824
|
for (const childRecord of Array.isArray(childRecords) ? childRecords : [childRecords]) {
|
|
1809
1825
|
if (childRecord === null) {
|
|
1810
1826
|
continue;
|
|
1811
1827
|
}
|
|
1812
|
-
const key = getRecordKey(asRecord(childRecord), childKeys);
|
|
1828
|
+
const key = getRecordKey(asRecord(childRecord), childKeys, mappers);
|
|
1813
1829
|
for (const parentRecord of parentMap[key] ?? []) {
|
|
1814
1830
|
if (joinExpr.isRelationUnique) {
|
|
1815
1831
|
parentRecord[joinExpr.parentField] = childRecord;
|
|
@@ -1821,6 +1837,40 @@ function attachChildrenToParents(parentRecords, children) {
|
|
|
1821
1837
|
}
|
|
1822
1838
|
return parentRecords;
|
|
1823
1839
|
}
|
|
1840
|
+
function inferKeyCasts(rows, keys) {
|
|
1841
|
+
function getKeyCast(type) {
|
|
1842
|
+
switch (type) {
|
|
1843
|
+
case "number":
|
|
1844
|
+
return Number;
|
|
1845
|
+
case "string":
|
|
1846
|
+
return String;
|
|
1847
|
+
case "boolean":
|
|
1848
|
+
return Boolean;
|
|
1849
|
+
case "bigint":
|
|
1850
|
+
return BigInt;
|
|
1851
|
+
default:
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
const keyCasts = Array.from({ length: keys.length });
|
|
1856
|
+
let keysFound = 0;
|
|
1857
|
+
for (const parent of rows) {
|
|
1858
|
+
const parentRecord = asRecord(parent);
|
|
1859
|
+
for (const [i, key] of keys.entries()) {
|
|
1860
|
+
if (parentRecord[key] !== null && keyCasts[i] === void 0) {
|
|
1861
|
+
const keyCast = getKeyCast(typeof parentRecord[key]);
|
|
1862
|
+
if (keyCast !== void 0) {
|
|
1863
|
+
keyCasts[i] = keyCast;
|
|
1864
|
+
}
|
|
1865
|
+
keysFound++;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
if (keysFound === keys.length) {
|
|
1869
|
+
break;
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
return keyCasts;
|
|
1873
|
+
}
|
|
1824
1874
|
function evalFieldInitializer(initializer, lastInsertId, scope, generators) {
|
|
1825
1875
|
switch (initializer.type) {
|
|
1826
1876
|
case "value":
|
|
@@ -1880,6 +1930,9 @@ function evaluateProcessingParameters(ops, scope, generators) {
|
|
|
1880
1930
|
evaluateProcessingParameters(nested, scope, generators);
|
|
1881
1931
|
}
|
|
1882
1932
|
}
|
|
1933
|
+
function cloneObject(value) {
|
|
1934
|
+
return klona2(value);
|
|
1935
|
+
}
|
|
1883
1936
|
|
|
1884
1937
|
// src/raw-json-protocol.ts
|
|
1885
1938
|
import { Decimal as Decimal4 } from "@prisma/client-runtime-utils";
|
|
@@ -2036,13 +2089,42 @@ var TransactionManager = class {
|
|
|
2036
2089
|
);
|
|
2037
2090
|
}
|
|
2038
2091
|
async #startTransactionImpl(options) {
|
|
2092
|
+
if (options.newTxId) {
|
|
2093
|
+
return await this.#withActiveTransactionLock(options.newTxId, "start", async (existing) => {
|
|
2094
|
+
if (existing.status !== "running") {
|
|
2095
|
+
throw new TransactionInternalConsistencyError(
|
|
2096
|
+
`Transaction in invalid state ${existing.status} when starting a nested transaction.`
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
if (!existing.transaction) {
|
|
2100
|
+
throw new TransactionInternalConsistencyError(
|
|
2101
|
+
`Transaction missing underlying driver transaction when starting a nested transaction.`
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
2104
|
+
existing.depth += 1;
|
|
2105
|
+
const savepointName = this.#nextSavepointName(existing);
|
|
2106
|
+
existing.savepoints.push(savepointName);
|
|
2107
|
+
try {
|
|
2108
|
+
await this.#requiredCreateSavepoint(existing.transaction)(savepointName);
|
|
2109
|
+
} catch (e) {
|
|
2110
|
+
existing.depth -= 1;
|
|
2111
|
+
existing.savepoints.pop();
|
|
2112
|
+
throw e;
|
|
2113
|
+
}
|
|
2114
|
+
return { id: existing.id };
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2039
2117
|
const transaction = {
|
|
2040
2118
|
id: await randomUUID(),
|
|
2041
2119
|
status: "waiting",
|
|
2042
2120
|
timer: void 0,
|
|
2043
2121
|
timeout: options.timeout,
|
|
2044
2122
|
startedAt: Date.now(),
|
|
2045
|
-
transaction: void 0
|
|
2123
|
+
transaction: void 0,
|
|
2124
|
+
operationQueue: Promise.resolve(),
|
|
2125
|
+
depth: 1,
|
|
2126
|
+
savepoints: [],
|
|
2127
|
+
savepointCounter: 0
|
|
2046
2128
|
};
|
|
2047
2129
|
const abortController = new AbortController();
|
|
2048
2130
|
const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
|
|
@@ -2076,14 +2158,49 @@ var TransactionManager = class {
|
|
|
2076
2158
|
}
|
|
2077
2159
|
async commitTransaction(transactionId) {
|
|
2078
2160
|
return await this.tracingHelper.runInChildSpan("commit_transaction", async () => {
|
|
2079
|
-
|
|
2080
|
-
|
|
2161
|
+
await this.#withActiveTransactionLock(transactionId, "commit", async (txw) => {
|
|
2162
|
+
if (txw.depth > 1) {
|
|
2163
|
+
if (!txw.transaction) throw new TransactionNotFoundError();
|
|
2164
|
+
const savepointName = txw.savepoints.at(-1);
|
|
2165
|
+
if (!savepointName) {
|
|
2166
|
+
throw new TransactionInternalConsistencyError(
|
|
2167
|
+
`Missing savepoint for nested commit. Depth: ${txw.depth}, transactionId: ${txw.id}`
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
try {
|
|
2171
|
+
await this.#releaseSavepoint(txw.transaction, savepointName);
|
|
2172
|
+
} finally {
|
|
2173
|
+
txw.savepoints.pop();
|
|
2174
|
+
txw.depth -= 1;
|
|
2175
|
+
}
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
await this.#closeTransaction(txw, "committed");
|
|
2179
|
+
});
|
|
2081
2180
|
});
|
|
2082
2181
|
}
|
|
2083
2182
|
async rollbackTransaction(transactionId) {
|
|
2084
2183
|
return await this.tracingHelper.runInChildSpan("rollback_transaction", async () => {
|
|
2085
|
-
|
|
2086
|
-
|
|
2184
|
+
await this.#withActiveTransactionLock(transactionId, "rollback", async (txw) => {
|
|
2185
|
+
if (txw.depth > 1) {
|
|
2186
|
+
if (!txw.transaction) throw new TransactionNotFoundError();
|
|
2187
|
+
const savepointName = txw.savepoints.at(-1);
|
|
2188
|
+
if (!savepointName) {
|
|
2189
|
+
throw new TransactionInternalConsistencyError(
|
|
2190
|
+
`Missing savepoint for nested rollback. Depth: ${txw.depth}, transactionId: ${txw.id}`
|
|
2191
|
+
);
|
|
2192
|
+
}
|
|
2193
|
+
try {
|
|
2194
|
+
await this.#requiredRollbackToSavepoint(txw.transaction)(savepointName);
|
|
2195
|
+
await this.#releaseSavepoint(txw.transaction, savepointName);
|
|
2196
|
+
} finally {
|
|
2197
|
+
txw.savepoints.pop();
|
|
2198
|
+
txw.depth -= 1;
|
|
2199
|
+
}
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
await this.#closeTransaction(txw, "rolled_back");
|
|
2203
|
+
});
|
|
2087
2204
|
});
|
|
2088
2205
|
}
|
|
2089
2206
|
async getTransaction(txInfo, operation) {
|
|
@@ -2127,22 +2244,90 @@ var TransactionManager = class {
|
|
|
2127
2244
|
return transaction;
|
|
2128
2245
|
}
|
|
2129
2246
|
async cancelAllTransactions() {
|
|
2130
|
-
await Promise.allSettled(
|
|
2247
|
+
await Promise.allSettled(
|
|
2248
|
+
[...this.transactions.values()].map(
|
|
2249
|
+
(tx) => this.#runSerialized(tx, async () => {
|
|
2250
|
+
const current = this.transactions.get(tx.id);
|
|
2251
|
+
if (current) {
|
|
2252
|
+
await this.#closeTransaction(current, "rolled_back");
|
|
2253
|
+
}
|
|
2254
|
+
})
|
|
2255
|
+
)
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2258
|
+
#nextSavepointName(transaction) {
|
|
2259
|
+
return `prisma_sp_${transaction.savepointCounter++}`;
|
|
2260
|
+
}
|
|
2261
|
+
#requiredCreateSavepoint(transaction) {
|
|
2262
|
+
if (transaction.createSavepoint) {
|
|
2263
|
+
return transaction.createSavepoint.bind(transaction);
|
|
2264
|
+
}
|
|
2265
|
+
throw new TransactionManagerError(
|
|
2266
|
+
`Nested transactions are not supported by adapter "${transaction.adapterName}" (${transaction.provider}): createSavepoint is not implemented.`
|
|
2267
|
+
);
|
|
2268
|
+
}
|
|
2269
|
+
#requiredRollbackToSavepoint(transaction) {
|
|
2270
|
+
if (transaction.rollbackToSavepoint) {
|
|
2271
|
+
return transaction.rollbackToSavepoint.bind(transaction);
|
|
2272
|
+
}
|
|
2273
|
+
throw new TransactionManagerError(
|
|
2274
|
+
`Nested transactions are not supported by adapter "${transaction.adapterName}" (${transaction.provider}): rollbackToSavepoint is not implemented.`
|
|
2275
|
+
);
|
|
2276
|
+
}
|
|
2277
|
+
async #releaseSavepoint(transaction, name) {
|
|
2278
|
+
if (transaction.releaseSavepoint) {
|
|
2279
|
+
await transaction.releaseSavepoint(name);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
#debugTransactionAlreadyClosedOnTimeout(transactionId) {
|
|
2283
|
+
debug("Transaction already committed or rolled back when timeout happened.", transactionId);
|
|
2131
2284
|
}
|
|
2132
2285
|
#startTransactionTimeout(transactionId, timeout) {
|
|
2133
2286
|
const timeoutStartedAt = Date.now();
|
|
2134
2287
|
const timer = createTimeoutIfDefined(async () => {
|
|
2135
2288
|
debug("Transaction timed out.", { transactionId, timeoutStartedAt, timeout });
|
|
2136
2289
|
const tx = this.transactions.get(transactionId);
|
|
2137
|
-
if (tx
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2290
|
+
if (!tx) {
|
|
2291
|
+
this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
await this.#runSerialized(tx, async () => {
|
|
2295
|
+
const current = this.transactions.get(transactionId);
|
|
2296
|
+
if (current && ["running", "waiting"].includes(current.status)) {
|
|
2297
|
+
await this.#closeTransaction(current, "timed_out");
|
|
2298
|
+
} else {
|
|
2299
|
+
this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2142
2302
|
}, timeout);
|
|
2143
2303
|
timer?.unref?.();
|
|
2144
2304
|
return timer;
|
|
2145
2305
|
}
|
|
2306
|
+
// Any operation that mutates or closes a transaction must run through this lock so
|
|
2307
|
+
// status/savepoint/depth checks and updates happen against a stable view of state.
|
|
2308
|
+
async #withActiveTransactionLock(transactionId, operation, callback) {
|
|
2309
|
+
const tx = this.#getActiveOrClosingTransaction(transactionId, operation);
|
|
2310
|
+
return await this.#runSerialized(tx, async () => {
|
|
2311
|
+
const current = this.#getActiveOrClosingTransaction(transactionId, operation);
|
|
2312
|
+
return await callback(current);
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
// Serializes operations per transaction id to prevent interleaving across awaits.
|
|
2316
|
+
// This avoids races where one operation mutates savepoint/depth state while another
|
|
2317
|
+
// operation is suspended, which could otherwise corrupt cleanup logic.
|
|
2318
|
+
async #runSerialized(tx, callback) {
|
|
2319
|
+
const previousOperation = tx.operationQueue;
|
|
2320
|
+
let releaseOperationLock;
|
|
2321
|
+
tx.operationQueue = new Promise((resolve) => {
|
|
2322
|
+
releaseOperationLock = resolve;
|
|
2323
|
+
});
|
|
2324
|
+
await previousOperation;
|
|
2325
|
+
try {
|
|
2326
|
+
return await callback();
|
|
2327
|
+
} finally {
|
|
2328
|
+
releaseOperationLock();
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2146
2331
|
async #closeTransaction(tx, status) {
|
|
2147
2332
|
const createClosingPromise = async () => {
|
|
2148
2333
|
debug("Closing transaction.", { transactionId: tx.id, status });
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { InMemoryOps } from '../query-plan';
|
|
2
2
|
export declare function processRecords(value: unknown, ops: InMemoryOps): unknown;
|
|
3
|
-
export declare function getRecordKey(record: {}, fields: string[]): string;
|
|
3
|
+
export declare function getRecordKey(record: {}, fields: readonly string[], mappers?: ((value: unknown) => unknown)[]): string;
|
|
@@ -5,6 +5,7 @@ import { 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';
|
|
8
|
+
import { DeepReadonly } from '../utils';
|
|
8
9
|
import { Value } from './scope';
|
|
9
10
|
export type QueryInterpreterTransactionManager = {
|
|
10
11
|
enabled: true;
|
|
@@ -39,6 +40,6 @@ export declare class QueryInterpreter {
|
|
|
39
40
|
provider?: SchemaProvider;
|
|
40
41
|
connectionInfo?: ConnectionInfo;
|
|
41
42
|
}): QueryInterpreter;
|
|
42
|
-
run(queryPlan: QueryPlanNode
|
|
43
|
+
run(queryPlan: DeepReadonly<QueryPlanNode>, options: QueryRuntimeOptions): Promise<unknown>;
|
|
43
44
|
private interpretNode;
|
|
44
45
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SqlQuery } from '@prisma/driver-adapter-utils';
|
|
2
2
|
import { type QueryPlanDbQuery } from '../query-plan';
|
|
3
|
+
import { DeepReadonly } from '../utils';
|
|
3
4
|
import { GeneratorRegistrySnapshot } from './generators';
|
|
4
5
|
import { ScopeBindings } from './scope';
|
|
5
|
-
export declare function renderQuery(dbQuery: QueryPlanDbQuery
|
|
6
|
+
export declare function renderQuery(dbQuery: DeepReadonly<QueryPlanDbQuery>, scope: ScopeBindings, generators: GeneratorRegistrySnapshot, maxChunkSize?: number): DeepReadonly<SqlQuery>[];
|
|
6
7
|
export declare function evaluateArg(arg: unknown, scope: ScopeBindings, generators: GeneratorRegistrySnapshot): unknown;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { DataRule, ValidationError } from '../query-plan';
|
|
2
|
-
|
|
2
|
+
import { DeepReadonly } from '../utils';
|
|
3
|
+
export declare function performValidation(data: unknown, rules: DeepReadonly<DataRule[]>, error: ValidationError): void;
|
|
3
4
|
export declare function doesSatisfyRule(data: unknown, rule: DataRule): boolean;
|
package/dist/query-plan.d.ts
CHANGED
|
@@ -56,6 +56,9 @@ export type Fragment = {
|
|
|
56
56
|
type: 'parameter';
|
|
57
57
|
} | {
|
|
58
58
|
type: 'parameterTuple';
|
|
59
|
+
itemPrefix: string;
|
|
60
|
+
itemSeparator: string;
|
|
61
|
+
itemSuffix: string;
|
|
59
62
|
} | {
|
|
60
63
|
type: 'parameterTupleList';
|
|
61
64
|
itemPrefix: string;
|
|
@@ -121,6 +124,7 @@ export type QueryPlanNode = {
|
|
|
121
124
|
args: {
|
|
122
125
|
parent: QueryPlanNode;
|
|
123
126
|
children: JoinExpression[];
|
|
127
|
+
canAssumeStrictEquality: boolean;
|
|
124
128
|
};
|
|
125
129
|
} | {
|
|
126
130
|
type: 'mapField';
|
|
@@ -227,14 +231,14 @@ export type DataRule = {
|
|
|
227
231
|
type: 'never';
|
|
228
232
|
};
|
|
229
233
|
export type ValidationError = {
|
|
230
|
-
|
|
234
|
+
errorIdentifier: 'RELATION_VIOLATION';
|
|
231
235
|
context: {
|
|
232
236
|
relation: string;
|
|
233
237
|
modelA: string;
|
|
234
238
|
modelB: string;
|
|
235
239
|
};
|
|
236
240
|
} | {
|
|
237
|
-
|
|
241
|
+
errorIdentifier: 'MISSING_RELATED_RECORD';
|
|
238
242
|
context: {
|
|
239
243
|
model: string;
|
|
240
244
|
relation: string;
|
|
@@ -243,24 +247,24 @@ export type ValidationError = {
|
|
|
243
247
|
neededFor?: string;
|
|
244
248
|
};
|
|
245
249
|
} | {
|
|
246
|
-
|
|
250
|
+
errorIdentifier: 'MISSING_RECORD';
|
|
247
251
|
context: {
|
|
248
252
|
operation: string;
|
|
249
253
|
};
|
|
250
254
|
} | {
|
|
251
|
-
|
|
255
|
+
errorIdentifier: 'INCOMPLETE_CONNECT_INPUT';
|
|
252
256
|
context: {
|
|
253
257
|
expectedRows: number;
|
|
254
258
|
};
|
|
255
259
|
} | {
|
|
256
|
-
|
|
260
|
+
errorIdentifier: 'INCOMPLETE_CONNECT_OUTPUT';
|
|
257
261
|
context: {
|
|
258
262
|
expectedRows: number;
|
|
259
263
|
relation: string;
|
|
260
264
|
relationType: string;
|
|
261
265
|
};
|
|
262
266
|
} | {
|
|
263
|
-
|
|
267
|
+
errorIdentifier: 'RECORDS_NOT_CONNECTED';
|
|
264
268
|
context: {
|
|
265
269
|
relation: string;
|
|
266
270
|
parent: string;
|
package/dist/tracing.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type Context, type Span, type SpanOptions } from '@opentelemetry/api';
|
|
|
2
2
|
import type { SqlQuery } from '@prisma/driver-adapter-utils';
|
|
3
3
|
import { QueryEvent } from './events';
|
|
4
4
|
import type { SchemaProvider } from './schema';
|
|
5
|
+
import { DeepReadonly } from './utils';
|
|
5
6
|
export type SpanCallback<R> = (span?: Span, context?: Context) => R;
|
|
6
7
|
export type ExtendedSpanOptions = SpanOptions & {
|
|
7
8
|
name: string;
|
|
@@ -13,7 +14,7 @@ export interface TracingHelper {
|
|
|
13
14
|
export declare const noopTracingHelper: TracingHelper;
|
|
14
15
|
export declare function providerToOtelSystem(provider: SchemaProvider): string;
|
|
15
16
|
export declare function withQuerySpanAndEvent<T>({ query, tracingHelper, provider, onQuery, execute, }: {
|
|
16
|
-
query: SqlQuery
|
|
17
|
+
query: DeepReadonly<SqlQuery>;
|
|
17
18
|
tracingHelper: TracingHelper;
|
|
18
19
|
provider: SchemaProvider;
|
|
19
20
|
onQuery?: (event: QueryEvent) => void;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export type DeepReadonly<T> = T extends undefined | null | boolean | string | number | symbol | Function | Date ? T : T extends Array<infer U> ? ReadonlyArray<DeepReadonly<U>> : unknown extends T ? unknown : {
|
|
2
|
+
readonly [K in keyof T]: DeepReadonly<T[K]>;
|
|
3
|
+
};
|
|
4
|
+
export type DeepUnreadonly<T> = T extends undefined | null | boolean | string | number | symbol | Function | Date ? T : T extends ReadonlyArray<infer U> ? Array<DeepUnreadonly<U>> : unknown extends T ? unknown : {
|
|
5
|
+
-readonly [K in keyof T]: DeepUnreadonly<T[K]>;
|
|
6
|
+
};
|
|
1
7
|
export declare function assertNever(_: never, message: string): never;
|
|
2
8
|
/**
|
|
3
9
|
* Checks if two objects are deeply equal, recursively checking all properties for strict equality.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma/client-engine-runtime",
|
|
3
|
-
"version": "7.5.0
|
|
3
|
+
"version": "7.5.0",
|
|
4
4
|
"description": "This package is intended for Prisma's internal use",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -31,19 +31,16 @@
|
|
|
31
31
|
"nanoid": "5.1.5",
|
|
32
32
|
"ulid": "3.0.0",
|
|
33
33
|
"uuid": "11.1.0",
|
|
34
|
-
"@prisma/client-runtime-utils": "7.5.0
|
|
35
|
-
"@prisma/debug": "7.5.0
|
|
36
|
-
"@prisma/
|
|
37
|
-
"@prisma/
|
|
34
|
+
"@prisma/client-runtime-utils": "7.5.0",
|
|
35
|
+
"@prisma/debug": "7.5.0",
|
|
36
|
+
"@prisma/sqlcommenter": "7.5.0",
|
|
37
|
+
"@prisma/driver-adapter-utils": "7.5.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@codspeed/benchmark.js-plugin": "4.0.0",
|
|
41
41
|
"@types/benchmark": "2.1.5",
|
|
42
|
-
"@types/jest": "29.5.14",
|
|
43
42
|
"@types/node": "~20.19.24",
|
|
44
|
-
"benchmark": "2.1.4"
|
|
45
|
-
"jest": "29.7.0",
|
|
46
|
-
"jest-junit": "16.0.0"
|
|
43
|
+
"benchmark": "2.1.4"
|
|
47
44
|
},
|
|
48
45
|
"files": [
|
|
49
46
|
"dist"
|
|
@@ -52,6 +49,6 @@
|
|
|
52
49
|
"scripts": {
|
|
53
50
|
"dev": "DEV=true tsx helpers/build.ts",
|
|
54
51
|
"build": "tsx helpers/build.ts",
|
|
55
|
-
"test": "
|
|
52
|
+
"test": "vitest run"
|
|
56
53
|
}
|
|
57
54
|
}
|