@prisma/client-engine-runtime 7.5.0-dev.9 → 7.6.0-dev.1
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 +214 -26
- package/dist/index.mjs +214 -26
- 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) {
|
|
@@ -893,8 +896,11 @@ var GeneratorRegistry = class {
|
|
|
893
896
|
}
|
|
894
897
|
};
|
|
895
898
|
var NowGenerator = class {
|
|
896
|
-
#now
|
|
899
|
+
#now;
|
|
897
900
|
generate() {
|
|
901
|
+
if (this.#now === void 0) {
|
|
902
|
+
this.#now = /* @__PURE__ */ new Date();
|
|
903
|
+
}
|
|
898
904
|
return this.#now.toISOString();
|
|
899
905
|
}
|
|
900
906
|
};
|
|
@@ -1040,8 +1046,11 @@ function paginateSingleList(list, { cursor, skip, take }) {
|
|
|
1040
1046
|
const end = take !== null ? start + take : list.length;
|
|
1041
1047
|
return list.slice(start, end);
|
|
1042
1048
|
}
|
|
1043
|
-
function getRecordKey(record, fields) {
|
|
1044
|
-
|
|
1049
|
+
function getRecordKey(record, fields, mappers) {
|
|
1050
|
+
const array = fields.map(
|
|
1051
|
+
(field, index) => mappers?.[index] ? record[field] !== null ? mappers[index](record[field]) : null : record[field]
|
|
1052
|
+
);
|
|
1053
|
+
return JSON.stringify(array);
|
|
1045
1054
|
}
|
|
1046
1055
|
|
|
1047
1056
|
// src/query-plan.ts
|
|
@@ -1078,7 +1087,11 @@ function evaluateArg(arg, scope, generators) {
|
|
|
1078
1087
|
if (found === void 0) {
|
|
1079
1088
|
throw new Error(`Missing value for query variable ${arg.prisma__value.name}`);
|
|
1080
1089
|
}
|
|
1081
|
-
arg
|
|
1090
|
+
if (arg.prisma__value.type === "DateTime" && typeof found === "string") {
|
|
1091
|
+
arg = new Date(found);
|
|
1092
|
+
} else {
|
|
1093
|
+
arg = found;
|
|
1094
|
+
}
|
|
1082
1095
|
} else if (isPrismaValueGenerator(arg)) {
|
|
1083
1096
|
const { name, args } = arg.prisma__value;
|
|
1084
1097
|
const generator = generators[name];
|
|
@@ -1136,7 +1149,10 @@ function renderFragment(fragment, placeholderFormat, ctx) {
|
|
|
1136
1149
|
case "stringChunk":
|
|
1137
1150
|
return fragment.chunk;
|
|
1138
1151
|
case "parameterTuple": {
|
|
1139
|
-
const placeholders = fragment.value.length == 0 ? "NULL" : fragment.value.map(() =>
|
|
1152
|
+
const placeholders = fragment.value.length == 0 ? "NULL" : fragment.value.map(() => {
|
|
1153
|
+
const item = formatPlaceholder(placeholderFormat, ctx.placeholderNumber++);
|
|
1154
|
+
return `${fragment.itemPrefix}${item}${fragment.itemSuffix}`;
|
|
1155
|
+
}).join(fragment.itemSeparator);
|
|
1140
1156
|
return `(${placeholders})`;
|
|
1141
1157
|
}
|
|
1142
1158
|
case "parameterTupleList": {
|
|
@@ -1495,7 +1511,7 @@ function doesSatisfyRule(data, rule) {
|
|
|
1495
1511
|
}
|
|
1496
1512
|
}
|
|
1497
1513
|
function renderMessage(data, error) {
|
|
1498
|
-
switch (error.
|
|
1514
|
+
switch (error.errorIdentifier) {
|
|
1499
1515
|
case "RELATION_VIOLATION":
|
|
1500
1516
|
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
1517
|
case "MISSING_RECORD":
|
|
@@ -1515,7 +1531,7 @@ function renderMessage(data, error) {
|
|
|
1515
1531
|
}
|
|
1516
1532
|
}
|
|
1517
1533
|
function getErrorCode2(error) {
|
|
1518
|
-
switch (error.
|
|
1534
|
+
switch (error.errorIdentifier) {
|
|
1519
1535
|
case "RELATION_VIOLATION":
|
|
1520
1536
|
return "P2014";
|
|
1521
1537
|
case "RECORDS_NOT_CONNECTED":
|
|
@@ -1630,7 +1646,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1630
1646
|
sum += await this.#withQuerySpanAndEvent(
|
|
1631
1647
|
commentedQuery,
|
|
1632
1648
|
context.queryable,
|
|
1633
|
-
() => context.queryable.executeRaw(commentedQuery).catch(
|
|
1649
|
+
() => context.queryable.executeRaw(cloneObject(commentedQuery)).catch(
|
|
1634
1650
|
(err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
|
|
1635
1651
|
)
|
|
1636
1652
|
);
|
|
@@ -1645,7 +1661,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1645
1661
|
const result = await this.#withQuerySpanAndEvent(
|
|
1646
1662
|
commentedQuery,
|
|
1647
1663
|
context.queryable,
|
|
1648
|
-
() => context.queryable.queryRaw(commentedQuery).catch(
|
|
1664
|
+
() => context.queryable.queryRaw(cloneObject(commentedQuery)).catch(
|
|
1649
1665
|
(err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
|
|
1650
1666
|
)
|
|
1651
1667
|
);
|
|
@@ -1697,7 +1713,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1697
1713
|
childRecords: (await this.interpretNode(joinExpr.child, context)).value
|
|
1698
1714
|
}))
|
|
1699
1715
|
);
|
|
1700
|
-
return { value: attachChildrenToParents(parent, children), lastInsertId };
|
|
1716
|
+
return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
|
|
1701
1717
|
}
|
|
1702
1718
|
case "transaction": {
|
|
1703
1719
|
if (!context.transactionManager.enabled) {
|
|
@@ -1744,8 +1760,9 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1744
1760
|
}
|
|
1745
1761
|
case "process": {
|
|
1746
1762
|
const { value, lastInsertId } = await this.interpretNode(node.args.expr, context);
|
|
1747
|
-
|
|
1748
|
-
|
|
1763
|
+
const ops = cloneObject(node.args.operations);
|
|
1764
|
+
evaluateProcessingParameters(ops, context.scope, context.generators);
|
|
1765
|
+
return { value: processRecords(value, ops), lastInsertId };
|
|
1749
1766
|
}
|
|
1750
1767
|
case "initializeRecord": {
|
|
1751
1768
|
const { lastInsertId } = await this.interpretNode(node.args.expr, context);
|
|
@@ -1838,12 +1855,13 @@ function mapField2(value, field) {
|
|
|
1838
1855
|
}
|
|
1839
1856
|
return value;
|
|
1840
1857
|
}
|
|
1841
|
-
function attachChildrenToParents(parentRecords, children) {
|
|
1858
|
+
function attachChildrenToParents(parentRecords, children, canAssumeStrictEquality) {
|
|
1842
1859
|
for (const { joinExpr, childRecords } of children) {
|
|
1843
1860
|
const parentKeys = joinExpr.on.map(([k]) => k);
|
|
1844
1861
|
const childKeys = joinExpr.on.map(([, k]) => k);
|
|
1845
1862
|
const parentMap = {};
|
|
1846
|
-
|
|
1863
|
+
const parentArray = Array.isArray(parentRecords) ? parentRecords : [parentRecords];
|
|
1864
|
+
for (const parent of parentArray) {
|
|
1847
1865
|
const parentRecord = asRecord(parent);
|
|
1848
1866
|
const key = getRecordKey(parentRecord, parentKeys);
|
|
1849
1867
|
if (!parentMap[key]) {
|
|
@@ -1856,11 +1874,12 @@ function attachChildrenToParents(parentRecords, children) {
|
|
|
1856
1874
|
parentRecord[joinExpr.parentField] = [];
|
|
1857
1875
|
}
|
|
1858
1876
|
}
|
|
1877
|
+
const mappers = canAssumeStrictEquality ? void 0 : inferKeyCasts(parentArray, parentKeys);
|
|
1859
1878
|
for (const childRecord of Array.isArray(childRecords) ? childRecords : [childRecords]) {
|
|
1860
1879
|
if (childRecord === null) {
|
|
1861
1880
|
continue;
|
|
1862
1881
|
}
|
|
1863
|
-
const key = getRecordKey(asRecord(childRecord), childKeys);
|
|
1882
|
+
const key = getRecordKey(asRecord(childRecord), childKeys, mappers);
|
|
1864
1883
|
for (const parentRecord of parentMap[key] ?? []) {
|
|
1865
1884
|
if (joinExpr.isRelationUnique) {
|
|
1866
1885
|
parentRecord[joinExpr.parentField] = childRecord;
|
|
@@ -1872,6 +1891,40 @@ function attachChildrenToParents(parentRecords, children) {
|
|
|
1872
1891
|
}
|
|
1873
1892
|
return parentRecords;
|
|
1874
1893
|
}
|
|
1894
|
+
function inferKeyCasts(rows, keys) {
|
|
1895
|
+
function getKeyCast(type) {
|
|
1896
|
+
switch (type) {
|
|
1897
|
+
case "number":
|
|
1898
|
+
return Number;
|
|
1899
|
+
case "string":
|
|
1900
|
+
return String;
|
|
1901
|
+
case "boolean":
|
|
1902
|
+
return Boolean;
|
|
1903
|
+
case "bigint":
|
|
1904
|
+
return BigInt;
|
|
1905
|
+
default:
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
const keyCasts = Array.from({ length: keys.length });
|
|
1910
|
+
let keysFound = 0;
|
|
1911
|
+
for (const parent of rows) {
|
|
1912
|
+
const parentRecord = asRecord(parent);
|
|
1913
|
+
for (const [i, key] of keys.entries()) {
|
|
1914
|
+
if (parentRecord[key] !== null && keyCasts[i] === void 0) {
|
|
1915
|
+
const keyCast = getKeyCast(typeof parentRecord[key]);
|
|
1916
|
+
if (keyCast !== void 0) {
|
|
1917
|
+
keyCasts[i] = keyCast;
|
|
1918
|
+
}
|
|
1919
|
+
keysFound++;
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
if (keysFound === keys.length) {
|
|
1923
|
+
break;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
return keyCasts;
|
|
1927
|
+
}
|
|
1875
1928
|
function evalFieldInitializer(initializer, lastInsertId, scope, generators) {
|
|
1876
1929
|
switch (initializer.type) {
|
|
1877
1930
|
case "value":
|
|
@@ -1931,6 +1984,9 @@ function evaluateProcessingParameters(ops, scope, generators) {
|
|
|
1931
1984
|
evaluateProcessingParameters(nested, scope, generators);
|
|
1932
1985
|
}
|
|
1933
1986
|
}
|
|
1987
|
+
function cloneObject(value) {
|
|
1988
|
+
return (0, import_klona2.klona)(value);
|
|
1989
|
+
}
|
|
1934
1990
|
|
|
1935
1991
|
// src/raw-json-protocol.ts
|
|
1936
1992
|
var import_client_runtime_utils4 = require("@prisma/client-runtime-utils");
|
|
@@ -2087,13 +2143,42 @@ var TransactionManager = class {
|
|
|
2087
2143
|
);
|
|
2088
2144
|
}
|
|
2089
2145
|
async #startTransactionImpl(options) {
|
|
2146
|
+
if (options.newTxId) {
|
|
2147
|
+
return await this.#withActiveTransactionLock(options.newTxId, "start", async (existing) => {
|
|
2148
|
+
if (existing.status !== "running") {
|
|
2149
|
+
throw new TransactionInternalConsistencyError(
|
|
2150
|
+
`Transaction in invalid state ${existing.status} when starting a nested transaction.`
|
|
2151
|
+
);
|
|
2152
|
+
}
|
|
2153
|
+
if (!existing.transaction) {
|
|
2154
|
+
throw new TransactionInternalConsistencyError(
|
|
2155
|
+
`Transaction missing underlying driver transaction when starting a nested transaction.`
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
existing.depth += 1;
|
|
2159
|
+
const savepointName = this.#nextSavepointName(existing);
|
|
2160
|
+
existing.savepoints.push(savepointName);
|
|
2161
|
+
try {
|
|
2162
|
+
await this.#requiredCreateSavepoint(existing.transaction)(savepointName);
|
|
2163
|
+
} catch (e) {
|
|
2164
|
+
existing.depth -= 1;
|
|
2165
|
+
existing.savepoints.pop();
|
|
2166
|
+
throw e;
|
|
2167
|
+
}
|
|
2168
|
+
return { id: existing.id };
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2090
2171
|
const transaction = {
|
|
2091
2172
|
id: await randomUUID(),
|
|
2092
2173
|
status: "waiting",
|
|
2093
2174
|
timer: void 0,
|
|
2094
2175
|
timeout: options.timeout,
|
|
2095
2176
|
startedAt: Date.now(),
|
|
2096
|
-
transaction: void 0
|
|
2177
|
+
transaction: void 0,
|
|
2178
|
+
operationQueue: Promise.resolve(),
|
|
2179
|
+
depth: 1,
|
|
2180
|
+
savepoints: [],
|
|
2181
|
+
savepointCounter: 0
|
|
2097
2182
|
};
|
|
2098
2183
|
const abortController = new AbortController();
|
|
2099
2184
|
const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
|
|
@@ -2127,14 +2212,49 @@ var TransactionManager = class {
|
|
|
2127
2212
|
}
|
|
2128
2213
|
async commitTransaction(transactionId) {
|
|
2129
2214
|
return await this.tracingHelper.runInChildSpan("commit_transaction", async () => {
|
|
2130
|
-
|
|
2131
|
-
|
|
2215
|
+
await this.#withActiveTransactionLock(transactionId, "commit", async (txw) => {
|
|
2216
|
+
if (txw.depth > 1) {
|
|
2217
|
+
if (!txw.transaction) throw new TransactionNotFoundError();
|
|
2218
|
+
const savepointName = txw.savepoints.at(-1);
|
|
2219
|
+
if (!savepointName) {
|
|
2220
|
+
throw new TransactionInternalConsistencyError(
|
|
2221
|
+
`Missing savepoint for nested commit. Depth: ${txw.depth}, transactionId: ${txw.id}`
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
try {
|
|
2225
|
+
await this.#releaseSavepoint(txw.transaction, savepointName);
|
|
2226
|
+
} finally {
|
|
2227
|
+
txw.savepoints.pop();
|
|
2228
|
+
txw.depth -= 1;
|
|
2229
|
+
}
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
await this.#closeTransaction(txw, "committed");
|
|
2233
|
+
});
|
|
2132
2234
|
});
|
|
2133
2235
|
}
|
|
2134
2236
|
async rollbackTransaction(transactionId) {
|
|
2135
2237
|
return await this.tracingHelper.runInChildSpan("rollback_transaction", async () => {
|
|
2136
|
-
|
|
2137
|
-
|
|
2238
|
+
await this.#withActiveTransactionLock(transactionId, "rollback", async (txw) => {
|
|
2239
|
+
if (txw.depth > 1) {
|
|
2240
|
+
if (!txw.transaction) throw new TransactionNotFoundError();
|
|
2241
|
+
const savepointName = txw.savepoints.at(-1);
|
|
2242
|
+
if (!savepointName) {
|
|
2243
|
+
throw new TransactionInternalConsistencyError(
|
|
2244
|
+
`Missing savepoint for nested rollback. Depth: ${txw.depth}, transactionId: ${txw.id}`
|
|
2245
|
+
);
|
|
2246
|
+
}
|
|
2247
|
+
try {
|
|
2248
|
+
await this.#requiredRollbackToSavepoint(txw.transaction)(savepointName);
|
|
2249
|
+
await this.#releaseSavepoint(txw.transaction, savepointName);
|
|
2250
|
+
} finally {
|
|
2251
|
+
txw.savepoints.pop();
|
|
2252
|
+
txw.depth -= 1;
|
|
2253
|
+
}
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
await this.#closeTransaction(txw, "rolled_back");
|
|
2257
|
+
});
|
|
2138
2258
|
});
|
|
2139
2259
|
}
|
|
2140
2260
|
async getTransaction(txInfo, operation) {
|
|
@@ -2178,22 +2298,90 @@ var TransactionManager = class {
|
|
|
2178
2298
|
return transaction;
|
|
2179
2299
|
}
|
|
2180
2300
|
async cancelAllTransactions() {
|
|
2181
|
-
await Promise.allSettled(
|
|
2301
|
+
await Promise.allSettled(
|
|
2302
|
+
[...this.transactions.values()].map(
|
|
2303
|
+
(tx) => this.#runSerialized(tx, async () => {
|
|
2304
|
+
const current = this.transactions.get(tx.id);
|
|
2305
|
+
if (current) {
|
|
2306
|
+
await this.#closeTransaction(current, "rolled_back");
|
|
2307
|
+
}
|
|
2308
|
+
})
|
|
2309
|
+
)
|
|
2310
|
+
);
|
|
2311
|
+
}
|
|
2312
|
+
#nextSavepointName(transaction) {
|
|
2313
|
+
return `prisma_sp_${transaction.savepointCounter++}`;
|
|
2314
|
+
}
|
|
2315
|
+
#requiredCreateSavepoint(transaction) {
|
|
2316
|
+
if (transaction.createSavepoint) {
|
|
2317
|
+
return transaction.createSavepoint.bind(transaction);
|
|
2318
|
+
}
|
|
2319
|
+
throw new TransactionManagerError(
|
|
2320
|
+
`Nested transactions are not supported by adapter "${transaction.adapterName}" (${transaction.provider}): createSavepoint is not implemented.`
|
|
2321
|
+
);
|
|
2322
|
+
}
|
|
2323
|
+
#requiredRollbackToSavepoint(transaction) {
|
|
2324
|
+
if (transaction.rollbackToSavepoint) {
|
|
2325
|
+
return transaction.rollbackToSavepoint.bind(transaction);
|
|
2326
|
+
}
|
|
2327
|
+
throw new TransactionManagerError(
|
|
2328
|
+
`Nested transactions are not supported by adapter "${transaction.adapterName}" (${transaction.provider}): rollbackToSavepoint is not implemented.`
|
|
2329
|
+
);
|
|
2330
|
+
}
|
|
2331
|
+
async #releaseSavepoint(transaction, name) {
|
|
2332
|
+
if (transaction.releaseSavepoint) {
|
|
2333
|
+
await transaction.releaseSavepoint(name);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
#debugTransactionAlreadyClosedOnTimeout(transactionId) {
|
|
2337
|
+
debug("Transaction already committed or rolled back when timeout happened.", transactionId);
|
|
2182
2338
|
}
|
|
2183
2339
|
#startTransactionTimeout(transactionId, timeout) {
|
|
2184
2340
|
const timeoutStartedAt = Date.now();
|
|
2185
2341
|
const timer = createTimeoutIfDefined(async () => {
|
|
2186
2342
|
debug("Transaction timed out.", { transactionId, timeoutStartedAt, timeout });
|
|
2187
2343
|
const tx = this.transactions.get(transactionId);
|
|
2188
|
-
if (tx
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2344
|
+
if (!tx) {
|
|
2345
|
+
this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
await this.#runSerialized(tx, async () => {
|
|
2349
|
+
const current = this.transactions.get(transactionId);
|
|
2350
|
+
if (current && ["running", "waiting"].includes(current.status)) {
|
|
2351
|
+
await this.#closeTransaction(current, "timed_out");
|
|
2352
|
+
} else {
|
|
2353
|
+
this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
|
|
2354
|
+
}
|
|
2355
|
+
});
|
|
2193
2356
|
}, timeout);
|
|
2194
2357
|
timer?.unref?.();
|
|
2195
2358
|
return timer;
|
|
2196
2359
|
}
|
|
2360
|
+
// Any operation that mutates or closes a transaction must run through this lock so
|
|
2361
|
+
// status/savepoint/depth checks and updates happen against a stable view of state.
|
|
2362
|
+
async #withActiveTransactionLock(transactionId, operation, callback) {
|
|
2363
|
+
const tx = this.#getActiveOrClosingTransaction(transactionId, operation);
|
|
2364
|
+
return await this.#runSerialized(tx, async () => {
|
|
2365
|
+
const current = this.#getActiveOrClosingTransaction(transactionId, operation);
|
|
2366
|
+
return await callback(current);
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
// Serializes operations per transaction id to prevent interleaving across awaits.
|
|
2370
|
+
// This avoids races where one operation mutates savepoint/depth state while another
|
|
2371
|
+
// operation is suspended, which could otherwise corrupt cleanup logic.
|
|
2372
|
+
async #runSerialized(tx, callback) {
|
|
2373
|
+
const previousOperation = tx.operationQueue;
|
|
2374
|
+
let releaseOperationLock;
|
|
2375
|
+
tx.operationQueue = new Promise((resolve) => {
|
|
2376
|
+
releaseOperationLock = resolve;
|
|
2377
|
+
});
|
|
2378
|
+
await previousOperation;
|
|
2379
|
+
try {
|
|
2380
|
+
return await callback();
|
|
2381
|
+
} finally {
|
|
2382
|
+
releaseOperationLock();
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2197
2385
|
async #closeTransaction(tx, status) {
|
|
2198
2386
|
const createClosingPromise = async () => {
|
|
2199
2387
|
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) {
|
|
@@ -842,8 +845,11 @@ var GeneratorRegistry = class {
|
|
|
842
845
|
}
|
|
843
846
|
};
|
|
844
847
|
var NowGenerator = class {
|
|
845
|
-
#now
|
|
848
|
+
#now;
|
|
846
849
|
generate() {
|
|
850
|
+
if (this.#now === void 0) {
|
|
851
|
+
this.#now = /* @__PURE__ */ new Date();
|
|
852
|
+
}
|
|
847
853
|
return this.#now.toISOString();
|
|
848
854
|
}
|
|
849
855
|
};
|
|
@@ -989,8 +995,11 @@ function paginateSingleList(list, { cursor, skip, take }) {
|
|
|
989
995
|
const end = take !== null ? start + take : list.length;
|
|
990
996
|
return list.slice(start, end);
|
|
991
997
|
}
|
|
992
|
-
function getRecordKey(record, fields) {
|
|
993
|
-
|
|
998
|
+
function getRecordKey(record, fields, mappers) {
|
|
999
|
+
const array = fields.map(
|
|
1000
|
+
(field, index) => mappers?.[index] ? record[field] !== null ? mappers[index](record[field]) : null : record[field]
|
|
1001
|
+
);
|
|
1002
|
+
return JSON.stringify(array);
|
|
994
1003
|
}
|
|
995
1004
|
|
|
996
1005
|
// src/query-plan.ts
|
|
@@ -1027,7 +1036,11 @@ function evaluateArg(arg, scope, generators) {
|
|
|
1027
1036
|
if (found === void 0) {
|
|
1028
1037
|
throw new Error(`Missing value for query variable ${arg.prisma__value.name}`);
|
|
1029
1038
|
}
|
|
1030
|
-
arg
|
|
1039
|
+
if (arg.prisma__value.type === "DateTime" && typeof found === "string") {
|
|
1040
|
+
arg = new Date(found);
|
|
1041
|
+
} else {
|
|
1042
|
+
arg = found;
|
|
1043
|
+
}
|
|
1031
1044
|
} else if (isPrismaValueGenerator(arg)) {
|
|
1032
1045
|
const { name, args } = arg.prisma__value;
|
|
1033
1046
|
const generator = generators[name];
|
|
@@ -1085,7 +1098,10 @@ function renderFragment(fragment, placeholderFormat, ctx) {
|
|
|
1085
1098
|
case "stringChunk":
|
|
1086
1099
|
return fragment.chunk;
|
|
1087
1100
|
case "parameterTuple": {
|
|
1088
|
-
const placeholders = fragment.value.length == 0 ? "NULL" : fragment.value.map(() =>
|
|
1101
|
+
const placeholders = fragment.value.length == 0 ? "NULL" : fragment.value.map(() => {
|
|
1102
|
+
const item = formatPlaceholder(placeholderFormat, ctx.placeholderNumber++);
|
|
1103
|
+
return `${fragment.itemPrefix}${item}${fragment.itemSuffix}`;
|
|
1104
|
+
}).join(fragment.itemSeparator);
|
|
1089
1105
|
return `(${placeholders})`;
|
|
1090
1106
|
}
|
|
1091
1107
|
case "parameterTupleList": {
|
|
@@ -1444,7 +1460,7 @@ function doesSatisfyRule(data, rule) {
|
|
|
1444
1460
|
}
|
|
1445
1461
|
}
|
|
1446
1462
|
function renderMessage(data, error) {
|
|
1447
|
-
switch (error.
|
|
1463
|
+
switch (error.errorIdentifier) {
|
|
1448
1464
|
case "RELATION_VIOLATION":
|
|
1449
1465
|
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
1466
|
case "MISSING_RECORD":
|
|
@@ -1464,7 +1480,7 @@ function renderMessage(data, error) {
|
|
|
1464
1480
|
}
|
|
1465
1481
|
}
|
|
1466
1482
|
function getErrorCode2(error) {
|
|
1467
|
-
switch (error.
|
|
1483
|
+
switch (error.errorIdentifier) {
|
|
1468
1484
|
case "RELATION_VIOLATION":
|
|
1469
1485
|
return "P2014";
|
|
1470
1486
|
case "RECORDS_NOT_CONNECTED":
|
|
@@ -1579,7 +1595,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1579
1595
|
sum += await this.#withQuerySpanAndEvent(
|
|
1580
1596
|
commentedQuery,
|
|
1581
1597
|
context.queryable,
|
|
1582
|
-
() => context.queryable.executeRaw(commentedQuery).catch(
|
|
1598
|
+
() => context.queryable.executeRaw(cloneObject(commentedQuery)).catch(
|
|
1583
1599
|
(err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
|
|
1584
1600
|
)
|
|
1585
1601
|
);
|
|
@@ -1594,7 +1610,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1594
1610
|
const result = await this.#withQuerySpanAndEvent(
|
|
1595
1611
|
commentedQuery,
|
|
1596
1612
|
context.queryable,
|
|
1597
|
-
() => context.queryable.queryRaw(commentedQuery).catch(
|
|
1613
|
+
() => context.queryable.queryRaw(cloneObject(commentedQuery)).catch(
|
|
1598
1614
|
(err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
|
|
1599
1615
|
)
|
|
1600
1616
|
);
|
|
@@ -1646,7 +1662,7 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1646
1662
|
childRecords: (await this.interpretNode(joinExpr.child, context)).value
|
|
1647
1663
|
}))
|
|
1648
1664
|
);
|
|
1649
|
-
return { value: attachChildrenToParents(parent, children), lastInsertId };
|
|
1665
|
+
return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
|
|
1650
1666
|
}
|
|
1651
1667
|
case "transaction": {
|
|
1652
1668
|
if (!context.transactionManager.enabled) {
|
|
@@ -1693,8 +1709,9 @@ var QueryInterpreter = class _QueryInterpreter {
|
|
|
1693
1709
|
}
|
|
1694
1710
|
case "process": {
|
|
1695
1711
|
const { value, lastInsertId } = await this.interpretNode(node.args.expr, context);
|
|
1696
|
-
|
|
1697
|
-
|
|
1712
|
+
const ops = cloneObject(node.args.operations);
|
|
1713
|
+
evaluateProcessingParameters(ops, context.scope, context.generators);
|
|
1714
|
+
return { value: processRecords(value, ops), lastInsertId };
|
|
1698
1715
|
}
|
|
1699
1716
|
case "initializeRecord": {
|
|
1700
1717
|
const { lastInsertId } = await this.interpretNode(node.args.expr, context);
|
|
@@ -1787,12 +1804,13 @@ function mapField2(value, field) {
|
|
|
1787
1804
|
}
|
|
1788
1805
|
return value;
|
|
1789
1806
|
}
|
|
1790
|
-
function attachChildrenToParents(parentRecords, children) {
|
|
1807
|
+
function attachChildrenToParents(parentRecords, children, canAssumeStrictEquality) {
|
|
1791
1808
|
for (const { joinExpr, childRecords } of children) {
|
|
1792
1809
|
const parentKeys = joinExpr.on.map(([k]) => k);
|
|
1793
1810
|
const childKeys = joinExpr.on.map(([, k]) => k);
|
|
1794
1811
|
const parentMap = {};
|
|
1795
|
-
|
|
1812
|
+
const parentArray = Array.isArray(parentRecords) ? parentRecords : [parentRecords];
|
|
1813
|
+
for (const parent of parentArray) {
|
|
1796
1814
|
const parentRecord = asRecord(parent);
|
|
1797
1815
|
const key = getRecordKey(parentRecord, parentKeys);
|
|
1798
1816
|
if (!parentMap[key]) {
|
|
@@ -1805,11 +1823,12 @@ function attachChildrenToParents(parentRecords, children) {
|
|
|
1805
1823
|
parentRecord[joinExpr.parentField] = [];
|
|
1806
1824
|
}
|
|
1807
1825
|
}
|
|
1826
|
+
const mappers = canAssumeStrictEquality ? void 0 : inferKeyCasts(parentArray, parentKeys);
|
|
1808
1827
|
for (const childRecord of Array.isArray(childRecords) ? childRecords : [childRecords]) {
|
|
1809
1828
|
if (childRecord === null) {
|
|
1810
1829
|
continue;
|
|
1811
1830
|
}
|
|
1812
|
-
const key = getRecordKey(asRecord(childRecord), childKeys);
|
|
1831
|
+
const key = getRecordKey(asRecord(childRecord), childKeys, mappers);
|
|
1813
1832
|
for (const parentRecord of parentMap[key] ?? []) {
|
|
1814
1833
|
if (joinExpr.isRelationUnique) {
|
|
1815
1834
|
parentRecord[joinExpr.parentField] = childRecord;
|
|
@@ -1821,6 +1840,40 @@ function attachChildrenToParents(parentRecords, children) {
|
|
|
1821
1840
|
}
|
|
1822
1841
|
return parentRecords;
|
|
1823
1842
|
}
|
|
1843
|
+
function inferKeyCasts(rows, keys) {
|
|
1844
|
+
function getKeyCast(type) {
|
|
1845
|
+
switch (type) {
|
|
1846
|
+
case "number":
|
|
1847
|
+
return Number;
|
|
1848
|
+
case "string":
|
|
1849
|
+
return String;
|
|
1850
|
+
case "boolean":
|
|
1851
|
+
return Boolean;
|
|
1852
|
+
case "bigint":
|
|
1853
|
+
return BigInt;
|
|
1854
|
+
default:
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
const keyCasts = Array.from({ length: keys.length });
|
|
1859
|
+
let keysFound = 0;
|
|
1860
|
+
for (const parent of rows) {
|
|
1861
|
+
const parentRecord = asRecord(parent);
|
|
1862
|
+
for (const [i, key] of keys.entries()) {
|
|
1863
|
+
if (parentRecord[key] !== null && keyCasts[i] === void 0) {
|
|
1864
|
+
const keyCast = getKeyCast(typeof parentRecord[key]);
|
|
1865
|
+
if (keyCast !== void 0) {
|
|
1866
|
+
keyCasts[i] = keyCast;
|
|
1867
|
+
}
|
|
1868
|
+
keysFound++;
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
if (keysFound === keys.length) {
|
|
1872
|
+
break;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
return keyCasts;
|
|
1876
|
+
}
|
|
1824
1877
|
function evalFieldInitializer(initializer, lastInsertId, scope, generators) {
|
|
1825
1878
|
switch (initializer.type) {
|
|
1826
1879
|
case "value":
|
|
@@ -1880,6 +1933,9 @@ function evaluateProcessingParameters(ops, scope, generators) {
|
|
|
1880
1933
|
evaluateProcessingParameters(nested, scope, generators);
|
|
1881
1934
|
}
|
|
1882
1935
|
}
|
|
1936
|
+
function cloneObject(value) {
|
|
1937
|
+
return klona2(value);
|
|
1938
|
+
}
|
|
1883
1939
|
|
|
1884
1940
|
// src/raw-json-protocol.ts
|
|
1885
1941
|
import { Decimal as Decimal4 } from "@prisma/client-runtime-utils";
|
|
@@ -2036,13 +2092,42 @@ var TransactionManager = class {
|
|
|
2036
2092
|
);
|
|
2037
2093
|
}
|
|
2038
2094
|
async #startTransactionImpl(options) {
|
|
2095
|
+
if (options.newTxId) {
|
|
2096
|
+
return await this.#withActiveTransactionLock(options.newTxId, "start", async (existing) => {
|
|
2097
|
+
if (existing.status !== "running") {
|
|
2098
|
+
throw new TransactionInternalConsistencyError(
|
|
2099
|
+
`Transaction in invalid state ${existing.status} when starting a nested transaction.`
|
|
2100
|
+
);
|
|
2101
|
+
}
|
|
2102
|
+
if (!existing.transaction) {
|
|
2103
|
+
throw new TransactionInternalConsistencyError(
|
|
2104
|
+
`Transaction missing underlying driver transaction when starting a nested transaction.`
|
|
2105
|
+
);
|
|
2106
|
+
}
|
|
2107
|
+
existing.depth += 1;
|
|
2108
|
+
const savepointName = this.#nextSavepointName(existing);
|
|
2109
|
+
existing.savepoints.push(savepointName);
|
|
2110
|
+
try {
|
|
2111
|
+
await this.#requiredCreateSavepoint(existing.transaction)(savepointName);
|
|
2112
|
+
} catch (e) {
|
|
2113
|
+
existing.depth -= 1;
|
|
2114
|
+
existing.savepoints.pop();
|
|
2115
|
+
throw e;
|
|
2116
|
+
}
|
|
2117
|
+
return { id: existing.id };
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
2039
2120
|
const transaction = {
|
|
2040
2121
|
id: await randomUUID(),
|
|
2041
2122
|
status: "waiting",
|
|
2042
2123
|
timer: void 0,
|
|
2043
2124
|
timeout: options.timeout,
|
|
2044
2125
|
startedAt: Date.now(),
|
|
2045
|
-
transaction: void 0
|
|
2126
|
+
transaction: void 0,
|
|
2127
|
+
operationQueue: Promise.resolve(),
|
|
2128
|
+
depth: 1,
|
|
2129
|
+
savepoints: [],
|
|
2130
|
+
savepointCounter: 0
|
|
2046
2131
|
};
|
|
2047
2132
|
const abortController = new AbortController();
|
|
2048
2133
|
const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
|
|
@@ -2076,14 +2161,49 @@ var TransactionManager = class {
|
|
|
2076
2161
|
}
|
|
2077
2162
|
async commitTransaction(transactionId) {
|
|
2078
2163
|
return await this.tracingHelper.runInChildSpan("commit_transaction", async () => {
|
|
2079
|
-
|
|
2080
|
-
|
|
2164
|
+
await this.#withActiveTransactionLock(transactionId, "commit", async (txw) => {
|
|
2165
|
+
if (txw.depth > 1) {
|
|
2166
|
+
if (!txw.transaction) throw new TransactionNotFoundError();
|
|
2167
|
+
const savepointName = txw.savepoints.at(-1);
|
|
2168
|
+
if (!savepointName) {
|
|
2169
|
+
throw new TransactionInternalConsistencyError(
|
|
2170
|
+
`Missing savepoint for nested commit. Depth: ${txw.depth}, transactionId: ${txw.id}`
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
try {
|
|
2174
|
+
await this.#releaseSavepoint(txw.transaction, savepointName);
|
|
2175
|
+
} finally {
|
|
2176
|
+
txw.savepoints.pop();
|
|
2177
|
+
txw.depth -= 1;
|
|
2178
|
+
}
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
await this.#closeTransaction(txw, "committed");
|
|
2182
|
+
});
|
|
2081
2183
|
});
|
|
2082
2184
|
}
|
|
2083
2185
|
async rollbackTransaction(transactionId) {
|
|
2084
2186
|
return await this.tracingHelper.runInChildSpan("rollback_transaction", async () => {
|
|
2085
|
-
|
|
2086
|
-
|
|
2187
|
+
await this.#withActiveTransactionLock(transactionId, "rollback", async (txw) => {
|
|
2188
|
+
if (txw.depth > 1) {
|
|
2189
|
+
if (!txw.transaction) throw new TransactionNotFoundError();
|
|
2190
|
+
const savepointName = txw.savepoints.at(-1);
|
|
2191
|
+
if (!savepointName) {
|
|
2192
|
+
throw new TransactionInternalConsistencyError(
|
|
2193
|
+
`Missing savepoint for nested rollback. Depth: ${txw.depth}, transactionId: ${txw.id}`
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
try {
|
|
2197
|
+
await this.#requiredRollbackToSavepoint(txw.transaction)(savepointName);
|
|
2198
|
+
await this.#releaseSavepoint(txw.transaction, savepointName);
|
|
2199
|
+
} finally {
|
|
2200
|
+
txw.savepoints.pop();
|
|
2201
|
+
txw.depth -= 1;
|
|
2202
|
+
}
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
await this.#closeTransaction(txw, "rolled_back");
|
|
2206
|
+
});
|
|
2087
2207
|
});
|
|
2088
2208
|
}
|
|
2089
2209
|
async getTransaction(txInfo, operation) {
|
|
@@ -2127,22 +2247,90 @@ var TransactionManager = class {
|
|
|
2127
2247
|
return transaction;
|
|
2128
2248
|
}
|
|
2129
2249
|
async cancelAllTransactions() {
|
|
2130
|
-
await Promise.allSettled(
|
|
2250
|
+
await Promise.allSettled(
|
|
2251
|
+
[...this.transactions.values()].map(
|
|
2252
|
+
(tx) => this.#runSerialized(tx, async () => {
|
|
2253
|
+
const current = this.transactions.get(tx.id);
|
|
2254
|
+
if (current) {
|
|
2255
|
+
await this.#closeTransaction(current, "rolled_back");
|
|
2256
|
+
}
|
|
2257
|
+
})
|
|
2258
|
+
)
|
|
2259
|
+
);
|
|
2260
|
+
}
|
|
2261
|
+
#nextSavepointName(transaction) {
|
|
2262
|
+
return `prisma_sp_${transaction.savepointCounter++}`;
|
|
2263
|
+
}
|
|
2264
|
+
#requiredCreateSavepoint(transaction) {
|
|
2265
|
+
if (transaction.createSavepoint) {
|
|
2266
|
+
return transaction.createSavepoint.bind(transaction);
|
|
2267
|
+
}
|
|
2268
|
+
throw new TransactionManagerError(
|
|
2269
|
+
`Nested transactions are not supported by adapter "${transaction.adapterName}" (${transaction.provider}): createSavepoint is not implemented.`
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
#requiredRollbackToSavepoint(transaction) {
|
|
2273
|
+
if (transaction.rollbackToSavepoint) {
|
|
2274
|
+
return transaction.rollbackToSavepoint.bind(transaction);
|
|
2275
|
+
}
|
|
2276
|
+
throw new TransactionManagerError(
|
|
2277
|
+
`Nested transactions are not supported by adapter "${transaction.adapterName}" (${transaction.provider}): rollbackToSavepoint is not implemented.`
|
|
2278
|
+
);
|
|
2279
|
+
}
|
|
2280
|
+
async #releaseSavepoint(transaction, name) {
|
|
2281
|
+
if (transaction.releaseSavepoint) {
|
|
2282
|
+
await transaction.releaseSavepoint(name);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
#debugTransactionAlreadyClosedOnTimeout(transactionId) {
|
|
2286
|
+
debug("Transaction already committed or rolled back when timeout happened.", transactionId);
|
|
2131
2287
|
}
|
|
2132
2288
|
#startTransactionTimeout(transactionId, timeout) {
|
|
2133
2289
|
const timeoutStartedAt = Date.now();
|
|
2134
2290
|
const timer = createTimeoutIfDefined(async () => {
|
|
2135
2291
|
debug("Transaction timed out.", { transactionId, timeoutStartedAt, timeout });
|
|
2136
2292
|
const tx = this.transactions.get(transactionId);
|
|
2137
|
-
if (tx
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2293
|
+
if (!tx) {
|
|
2294
|
+
this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
await this.#runSerialized(tx, async () => {
|
|
2298
|
+
const current = this.transactions.get(transactionId);
|
|
2299
|
+
if (current && ["running", "waiting"].includes(current.status)) {
|
|
2300
|
+
await this.#closeTransaction(current, "timed_out");
|
|
2301
|
+
} else {
|
|
2302
|
+
this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
|
|
2303
|
+
}
|
|
2304
|
+
});
|
|
2142
2305
|
}, timeout);
|
|
2143
2306
|
timer?.unref?.();
|
|
2144
2307
|
return timer;
|
|
2145
2308
|
}
|
|
2309
|
+
// Any operation that mutates or closes a transaction must run through this lock so
|
|
2310
|
+
// status/savepoint/depth checks and updates happen against a stable view of state.
|
|
2311
|
+
async #withActiveTransactionLock(transactionId, operation, callback) {
|
|
2312
|
+
const tx = this.#getActiveOrClosingTransaction(transactionId, operation);
|
|
2313
|
+
return await this.#runSerialized(tx, async () => {
|
|
2314
|
+
const current = this.#getActiveOrClosingTransaction(transactionId, operation);
|
|
2315
|
+
return await callback(current);
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
// Serializes operations per transaction id to prevent interleaving across awaits.
|
|
2319
|
+
// This avoids races where one operation mutates savepoint/depth state while another
|
|
2320
|
+
// operation is suspended, which could otherwise corrupt cleanup logic.
|
|
2321
|
+
async #runSerialized(tx, callback) {
|
|
2322
|
+
const previousOperation = tx.operationQueue;
|
|
2323
|
+
let releaseOperationLock;
|
|
2324
|
+
tx.operationQueue = new Promise((resolve) => {
|
|
2325
|
+
releaseOperationLock = resolve;
|
|
2326
|
+
});
|
|
2327
|
+
await previousOperation;
|
|
2328
|
+
try {
|
|
2329
|
+
return await callback();
|
|
2330
|
+
} finally {
|
|
2331
|
+
releaseOperationLock();
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2146
2334
|
async #closeTransaction(tx, status) {
|
|
2147
2335
|
const createClosingPromise = async () => {
|
|
2148
2336
|
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.
|
|
3
|
+
"version": "7.6.0-dev.1",
|
|
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.
|
|
35
|
-
"@prisma/debug": "7.
|
|
36
|
-
"@prisma/driver-adapter-utils": "7.
|
|
37
|
-
"@prisma/sqlcommenter": "7.
|
|
34
|
+
"@prisma/client-runtime-utils": "7.6.0-dev.1",
|
|
35
|
+
"@prisma/debug": "7.6.0-dev.1",
|
|
36
|
+
"@prisma/driver-adapter-utils": "7.6.0-dev.1",
|
|
37
|
+
"@prisma/sqlcommenter": "7.6.0-dev.1"
|
|
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
|
}
|