-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #69 from cerebruminc/lucianbuzzo/nest-transactions…
…-limitation chore: Add info on known limitations wrt nested transactions
- Loading branch information
Showing
6 changed files
with
183 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
prisma/migrations/20231011122225_add_account_model/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
-- CreateTable | ||
CREATE TABLE "Account" ( | ||
"id" SERIAL NOT NULL, | ||
"amount" INTEGER NOT NULL DEFAULT 0, | ||
"email" TEXT NOT NULL, | ||
|
||
CONSTRAINT "Account_pkey" PRIMARY KEY ("id") | ||
); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "Account_email_key" ON "Account"("email"); |
9 changes: 9 additions & 0 deletions
9
prisma/migrations/20231011122429_match_fields_names_with_docs/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/* | ||
Warnings: | ||
- You are about to drop the column `amount` on the `Account` table. All the data in the column will be lost. | ||
*/ | ||
-- AlterTable | ||
ALTER TABLE "Account" DROP COLUMN "amount", | ||
ADD COLUMN "balance" INTEGER NOT NULL DEFAULT 0; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { PrismaClient } from "@prisma/client"; | ||
import { v4 as uuid } from "uuid"; | ||
import { setup } from "../../src"; | ||
|
||
// This function is setup to demonstrate the behaviour of nested transactions and rollbacks in Prisma. | ||
// This example is based on interactive transaction docs on the Prisma website: | ||
// https://www.prisma.io/docs/concepts/components/prisma-client/transactions#interactive-transactions | ||
async function transfer(client: PrismaClient, from: string, to: string, amount: number) { | ||
return await client.$transaction(async (tx) => { | ||
// 1. Decrement amount from the sender. | ||
const sender = await tx.account.update({ | ||
data: { | ||
balance: { | ||
decrement: amount, | ||
}, | ||
}, | ||
where: { | ||
email: from, | ||
}, | ||
}); | ||
|
||
// 2. Verify that the sender's balance didn't go below zero. | ||
if (sender.balance < 0) { | ||
throw new Error(`${from} doesn't have enough to send ${amount}`); | ||
} | ||
|
||
// 3. Increment the recipient's balance by amount | ||
const recipient = await tx.account.update({ | ||
data: { | ||
balance: { | ||
increment: amount, | ||
}, | ||
}, | ||
where: { | ||
email: to, | ||
}, | ||
}); | ||
|
||
return recipient; | ||
}); | ||
} | ||
|
||
describe("nested transactions", () => { | ||
it("is expected to NOT rollback transactions if the outer transaction fails", async () => { | ||
const role = `USER_${uuid()}`; | ||
const client = await setup({ | ||
prisma: new PrismaClient(), | ||
getRoles(_abilities) { | ||
return { | ||
[role]: "*", | ||
}; | ||
}, | ||
getContext: () => ({ | ||
role, | ||
context: {}, | ||
}), | ||
}); | ||
|
||
const email1 = `alice-${uuid()}@example.com`; | ||
const account1 = await client.account.create({ | ||
data: { | ||
email: email1, | ||
balance: 100, | ||
}, | ||
}); | ||
const email2 = `bob-${uuid()}@example.com`; | ||
const account2 = await client.account.create({ | ||
data: { | ||
email: email2, | ||
balance: 100, | ||
}, | ||
}); | ||
|
||
// This transfer is successful | ||
await transfer(client as PrismaClient, email1, email2, 100); | ||
// This transfer fails because Alice doesn't have enough funds in her account | ||
await expect(transfer(client as PrismaClient, email1, email2, 100)).rejects.toThrow(); | ||
|
||
// Due to lack of nested transaction support, the first transfer is not rolled back | ||
// and the "from" account is still debited | ||
const result1 = await client.account.findUniqueOrThrow({ | ||
where: { | ||
id: account1.id, | ||
}, | ||
}); | ||
|
||
expect(result1.balance).toBe(-100); | ||
|
||
const result2 = await client.account.findUniqueOrThrow({ | ||
where: { | ||
id: account2.id, | ||
}, | ||
}); | ||
|
||
expect(result2.balance).toBe(200); | ||
}); | ||
|
||
it("should rollback transactions if the outer transaction fails if you bypass yates", async () => { | ||
const role = `USER_${uuid()}`; | ||
const client = await setup({ | ||
prisma: new PrismaClient(), | ||
getRoles(_abilities) { | ||
return { | ||
[role]: "*", | ||
}; | ||
}, | ||
// Returning null here bypasses yates | ||
getContext: () => null, | ||
}); | ||
|
||
const email1 = `alice-${uuid()}@example.com`; | ||
const account1 = await client.account.create({ | ||
data: { | ||
email: email1, | ||
balance: 100, | ||
}, | ||
}); | ||
const email2 = `bob-${uuid()}@example.com`; | ||
const account2 = await client.account.create({ | ||
data: { | ||
email: email2, | ||
balance: 100, | ||
}, | ||
}); | ||
|
||
// This transfer is successful | ||
await transfer(client as PrismaClient, email1, email2, 100); | ||
// This transfer fails because Alice doesn't have enough funds in her account | ||
await expect(transfer(client as PrismaClient, email1, email2, 100)).rejects.toThrow(); | ||
|
||
// Because we bypassed the Yates internal transaction, the rollback is successful | ||
// and the "from" account is never debited. | ||
const result1 = await client.account.findUniqueOrThrow({ | ||
where: { | ||
id: account1.id, | ||
}, | ||
}); | ||
|
||
expect(result1.balance).toBe(0); | ||
|
||
const result2 = await client.account.findUniqueOrThrow({ | ||
where: { | ||
id: account2.id, | ||
}, | ||
}); | ||
|
||
expect(result2.balance).toBe(200); | ||
}); | ||
}); |