Skip to content

Commit 89fbd77

Browse files
committed
fix: Throw TransactionError if database was disconnected when issuing transaction command
Automatically reconnecting for an in-transaction queries is not supported, as we’d have to replay all previous queries and still risk inconsistency if any subsequently issued application queries depended on data previously read during the disconnected transaction. We’d *also* have to keep a log of all previously returned data to verify that it hasn’t changed when reissuing the command – an unacceptable overhead, particularly since applications must be able to handle transaction conflicts anyway and this patch just makes disconnects look like any other “please retry transaction” condition. This commit also: * Removes useless `try…catch` blocks around function calls already containing an equivalent `try…catch` block and hence never catching anything. * Replaces calls to `commit()` in error handling with `rollback()` as `COMMIT`ing after an putting a transaction into an “aborted” state actually issues a `ROLLBACK` internally in PostgreSQL (probably some arcane compatiblity thing with software like this one), but using the wrong terminology is highly confusing. (*Actually* committing would risk applying inconsistent changes, resulting in data corruption!)
1 parent 412bbfb commit 89fbd77

1 file changed

Lines changed: 20 additions & 35 deletions

File tree

query/transaction.ts

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,9 @@ export class Transaction {
279279
await this.#client.queryArray(
280280
`BEGIN ${permissions} ISOLATION LEVEL ${isolation_level};${snapshot}`,
281281
);
282+
} else if (e instanceof PostgresError) {
283+
throw new TransactionError(this.name, e);
282284
} else {
283-
if (e instanceof PostgresError) {
284-
throw new TransactionError(this.name, e);
285-
}
286285
throw e;
287286
}
288287
}
@@ -333,16 +332,9 @@ export class Transaction {
333332
const chain = options?.chain ?? false;
334333

335334
if (!this.#committed) {
336-
try {
337-
await this.queryArray(`COMMIT ${chain ? "AND CHAIN" : ""}`);
338-
if (!chain) {
339-
this.#committed = true;
340-
}
341-
} catch (e) {
342-
if (e instanceof PostgresError) {
343-
throw new TransactionError(this.name, e);
344-
}
345-
throw e;
335+
await this.queryArray(`COMMIT ${chain ? "AND CHAIN" : ""}`);
336+
if (!chain) {
337+
this.#committed = true;
346338
}
347339
}
348340

@@ -513,7 +505,12 @@ export class Transaction {
513505
return (await this.#executeQuery(query)) as QueryArrayResult<T>;
514506
} catch (e) {
515507
if (e instanceof PostgresError) {
516-
await this.commit();
508+
await this.rollback();
509+
throw new TransactionError(this.name, e);
510+
} else if (e instanceof Deno.errors.BrokenPipe) {
511+
// Transaction was already rolled back by PostgreSQL on disconnect
512+
this.#resetTransaction();
513+
this.#updateClientLock(null);
517514
throw new TransactionError(this.name, e);
518515
}
519516
throw e;
@@ -615,7 +612,12 @@ export class Transaction {
615612
return (await this.#executeQuery(query)) as QueryObjectResult<T>;
616613
} catch (e) {
617614
if (e instanceof PostgresError) {
618-
await this.commit();
615+
await this.rollback();
616+
throw new TransactionError(this.name, e);
617+
} else if (e instanceof Deno.errors.BrokenPipe) {
618+
// Transaction was already rolled back by PostgreSQL on disconnect
619+
this.#resetTransaction();
620+
this.#updateClientLock(null);
619621
throw new TransactionError(this.name, e);
620622
}
621623
throw e;
@@ -755,15 +757,7 @@ export class Transaction {
755757

756758
// If no savepoint is provided, rollback the whole transaction and check for the chain operator
757759
// in order to decide whether to restart the transaction or end it
758-
try {
759-
await this.queryArray(`ROLLBACK ${chain_option ? "AND CHAIN" : ""}`);
760-
} catch (e) {
761-
if (e instanceof PostgresError) {
762-
await this.commit();
763-
throw new TransactionError(this.name, e);
764-
}
765-
throw e;
766-
}
760+
await this.queryArray(`ROLLBACK ${chain_option ? "AND CHAIN" : ""}`);
767761

768762
this.#resetTransaction();
769763
if (!chain_option) {
@@ -857,8 +851,7 @@ export class Transaction {
857851
try {
858852
await savepoint.update();
859853
} catch (e) {
860-
if (e instanceof PostgresError) {
861-
await this.commit();
854+
if (e instanceof PostgresError || e instanceof Deno.errors.BrokenPipe) {
862855
throw new TransactionError(this.name, e);
863856
}
864857
throw e;
@@ -874,15 +867,7 @@ export class Transaction {
874867
},
875868
);
876869

877-
try {
878-
await savepoint.update();
879-
} catch (e) {
880-
if (e instanceof PostgresError) {
881-
await this.commit();
882-
throw new TransactionError(this.name, e);
883-
}
884-
throw e;
885-
}
870+
await savepoint.update();
886871
this.#savepoints.push(savepoint);
887872
}
888873

0 commit comments

Comments
 (0)