From e205a1108991d6839b12e0e97dd2a2407223eece Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Tue, 22 Sep 2026 03:55:38 +0500 Subject: [PATCH 1/2] fix(client): stop nested $transaction from mutating the caller's options object Signed-off-by: Lazizbek Ergashev --- .../client/src/runtime/getPrismaClient.ts | 6 ++--- .../interactive-transactions/tests.ts | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/client/src/runtime/getPrismaClient.ts b/packages/client/src/runtime/getPrismaClient.ts index 5b71c916fccb..18784bbb479d 100644 --- a/packages/client/src/runtime/getPrismaClient.ts +++ b/packages/client/src/runtime/getPrismaClient.ts @@ -830,9 +830,6 @@ Or read our docs at https://www.prisma.io/docs/concepts/components/prisma-client if (activeScope !== itxContext.scopeId) { throw new Error('Concurrent nested transactions are not supported') } - - // Re-use the underlying transaction in the engine by reusing the same transaction id. - options.newTxId = itxContext.txId } scopeStack.push(scopeId) @@ -842,7 +839,8 @@ Or read our docs at https://www.prisma.io/docs/concepts/components/prisma-client maxWait: options?.maxWait ?? this._engineConfig.transactionOptions.maxWait, timeout: options?.timeout ?? this._engineConfig.transactionOptions.timeout, isolationLevel: options?.isolationLevel ?? this._engineConfig.transactionOptions.isolationLevel, - newTxId: options.newTxId, + // Re-use the underlying transaction in the engine by reusing the same transaction id. + newTxId: isNested ? itxContext.txId : undefined, } let info: Transaction.InteractiveTransactionInfo try { diff --git a/packages/client/tests/functional/interactive-transactions/tests.ts b/packages/client/tests/functional/interactive-transactions/tests.ts index 061d4a7cd3e4..22efa2ff7c5f 100644 --- a/packages/client/tests/functional/interactive-transactions/tests.ts +++ b/packages/client/tests/functional/interactive-transactions/tests.ts @@ -521,6 +521,29 @@ testMatrix.setupTestSuite( expect(users.map((u) => u.email)).toEqual([outerEmail1, outerEmail2].sort()) }) + testIf(provider !== Providers.MONGODB)('sql: nested transaction does not mutate the options object', async () => { + const email1 = `user_${copycat.uuid(251)}@website.com` + const email2 = `user_${copycat.uuid(252)}@website.com` + const options = { maxWait: 5000, timeout: 5000 } + + await prisma.$transaction(async (tx) => { + await tx.$transaction(async (tx2) => { + await tx2.user.create({ data: { email: email1 } }) + }, options) + }, options) + + expect(options).toEqual({ maxWait: 5000, timeout: 5000 }) + + await prisma.$transaction(async (tx) => { + await tx.user.create({ data: { email: email2 } }) + }, options) + + const users = await prisma.user.findMany({ + where: { email: { in: [email1, email2] } }, + }) + expect(users).toHaveLength(2) + }) + testIf(provider !== Providers.MONGODB)('sql: enforce order for nested transactions', async () => { const result = prisma.$transaction(async (tx) => { const nested = tx.$transaction(async (tx2) => { From 4389757b99a7ceb07627933cffbf2dffefa681fd Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Tue, 22 Sep 2026 04:07:00 +0500 Subject: [PATCH 2/2] test(client): assert the transaction options object strictly Signed-off-by: Lazizbek Ergashev --- .../client/tests/functional/interactive-transactions/tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/tests/functional/interactive-transactions/tests.ts b/packages/client/tests/functional/interactive-transactions/tests.ts index 22efa2ff7c5f..6c1bc7f53ce4 100644 --- a/packages/client/tests/functional/interactive-transactions/tests.ts +++ b/packages/client/tests/functional/interactive-transactions/tests.ts @@ -532,7 +532,7 @@ testMatrix.setupTestSuite( }, options) }, options) - expect(options).toEqual({ maxWait: 5000, timeout: 5000 }) + expect(options).toStrictEqual({ maxWait: 5000, timeout: 5000 }) await prisma.$transaction(async (tx) => { await tx.user.create({ data: { email: email2 } })