Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/selfhost/pg-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ export function createPgAdapter(pool: Pool): D1Database {
await client.query("COMMIT");
return out;
} catch (error) {
await client.query("ROLLBACK");
try {
await client.query("ROLLBACK");
} catch {
/* ignore */
}
throw error;
} finally {
client.release();
Expand Down
32 changes: 32 additions & 0 deletions test/integration/selfhost-pg.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,38 @@ suite("Postgres backend (#977) — real Postgres", () => {
expect(still?.n).toBe(1); // the DELETE rolled back
});

it("regression: a rollback failure never masks the original batch error (#6282)", async () => {
// Force the ROLLBACK itself to throw, simulating the exact scenario (a connection failure) where the
// original error and a subsequent rollback failure would otherwise race to be the thrown error. Only
// ROLLBACK is intercepted -- every other query still runs for real against the live Postgres instance.
const realClient = await pool.connect();
const originalQuery = realClient.query.bind(realClient);
let rollbackAttempted = false;
try {
// @ts-expect-error -- intentionally narrowing pg's overloaded query() signature for this simulation
realClient.query = async (...args: Parameters<typeof originalQuery>) => {
if (args[0] === "ROLLBACK") {
rollbackAttempted = true;
throw new Error("simulated rollback failure");
}
return originalQuery(...args);
};
const fakePool = { connect: async () => realClient } as unknown as pg.Pool;
const db = createPgAdapter(fakePool);

await expect(
db.batch([
db.prepare("INSERT INTO system_flags (key, value) VALUES (?, ?) , bad-sql").bind("z", "1"), // syntax error
]),
).rejects.toThrow(/bad-sql|syntax/i); // the ORIGINAL error surfaces, not "simulated rollback failure"
expect(rollbackAttempted).toBe(true);
} finally {
// batch()'s own `finally` already released this client back to the pool -- just restore the
// query override so the shared pool's client isn't left poisoned for later tests.
realClient.query = originalQuery;
}
});

it("prunes rows past the retention window and processJob('prune-retention') does not dead-letter (regression for the live self-host incident: job _selfhost_jobs.id=61132 failed with 'column \"rowid\" does not exist')", async () => {
const db = createPgAdapter(pool);
const env = { DB: db } as unknown as Env;
Expand Down
Loading