-
Couldn't load subscription status.
- Fork 350
Description
This is related to this issue: #899, but that issue was marked invalid and closed.
When using an await Promise.all construction inside a transaction, an error that is thrown inside the one of the promises causes the await Promise.all to fail, but the transaction does not properly get rolled back.
I managed to create a reproducible example in the form of a unit test here:
describe("Transactions", () => {
beforeEach(async () => {
await db.schema
.createTable("transactions_promises_tests")
.addColumn("name", "text")
.execute();
});
afterEach(async () => {
await db.schema.dropTable("transactions_promises_tests").execute();
});
it("rollback when a Promise inside Promise.all fails", async () => {
const names = ["test1", "test2", "error"];
const transaction = db.transaction().execute(async (trx) => {
await Promise.all(
names.map(async (name) => {
if (name === "error") throw new Error("Test error");
await trx
.insertInto("transactions_promises_tests")
.values([{ name }])
.execute();
}),
);
});
await expect(transaction).rejects.toThrow("Test error");
const tableContents = await db
.selectFrom("transactions_promises_tests")
.selectAll()
.execute();
// This fails
expect(tableContents.length).toBe(0);
});
it("rollback when a Promise inside a for-loop containing 'await [Promise]' fails", async () => {
const names = ["test1", "test2", "error"];
const transaction = db.transaction().execute(async (trx) => {
for (const name of names) {
if (name === "error") throw new Error("Test error");
await trx
.insertInto("transactions_promises_tests")
.values([{ name }])
.execute();
}
});
await expect(transaction).rejects.toThrow("Test error");
const tableContents = await db
.selectFrom("transactions_promises_tests")
.selectAll()
.execute();
// This succeeds
expect(tableContents.length).toBe(0);
});
});I first noticed this problem when using a Promise.all construction inside a migration. Then I discovered the problem applies to transactions in general. My current workaround is to rewrite all my Promise.all logic to the second construction shown in the above example, which is just a simple for (...) { await (...) } construction.