5

I am using pg-promise to run my SQL queries. The queries themselves are stored in external .sql files.

When I execute a transaction, Postgres will abort the transaction if an error occurs (as expected). The problem that I'm running into is any separate queries I try to run after the transaction is aborted are not run and I instead get this message: "current transaction is aborted, commands ignored until end of transaction block". If the queries were being run in the psql console, I could solve this problem by issuing a ROLLBACK after the failed query. I don't think that's an option here since the SQL used by my application is located in an external file. I also don't think Savepoints are an option because the entire transaction should be thrown out if something fails.

How would I rollback in the SQL file if this error occurs?

Here's the SQL for reference:

BEGIN;

DELETE 
FROM tournament_tossup_values
WHERE tournament_id = $1 AND
NOT EXISTS
(
    SELECT id
    FROM tournament_match
    WHERE tournament_id = $1
);

UPDATE tournament
SET bonus_point_value = $5, parts_per_bonus = $6
WHERE id = $1 AND NOT EXISTS (
    SELECT id 
    FROM tournament_match
    WHERE tournament_id = $1
)
RETURNING bonus_point_value, parts_per_bonus; <-- Any subsequent accesses to the database by the application fail if this transaction fails

COMMIT;  <-- I want to rollback everything if this fails

Thank you in advance!

mbhuiyan
  • 145
  • 2
  • 12
  • You are referring to library [pg-promise](https://github.com/vitaly-t/pg-promise), but you are not using its support for transactions - method `tx`? In this case we can't be sure what is going on, as you do not show the complete code of what you are doing. Perhaps it is best that you use method `tx`, and execute queries in it as shown in all examples. See [Transactions](https://github.com/vitaly-t/pg-promise#transactions). This will make your code more predictable, since you do not provide a proper `ROLLBACK` logic in your SQL anyway. – vitaly-t Jul 25 '16 at 02:01

1 Answers1

5

When implementing a transaction in an external SQL file you need to provide all the proper handling for COMMIT and ROLLBACK. When you do not do that, the transaction status may become unpredictable inside your server-side code, and result in the type of errors that you are getting.

This can be a bit tricky, and easier said than done. This is why the best solution is not to do it at all.

Module pg-promise that you are already using provides reliable handling for transactions, via method tx, which is what you should be using.

From the tx method documentation:

Executes a callback function as a transaction (...)

A transaction wraps a regular task into additional queries:

it executes BEGIN just before invoking the callback function

it executes COMMIT, if the callback didn't throw any error or return a rejected promise, it executes ROLLBACK, if the callback did throw an error or return a rejected promise

it executes corresponding SAVEPOINT commands when the method is called recursively.

To that end, split your SQL file into two files - one with your DELETE operation and one with your UPDATE operation, and then execute them as two queries inside a transaction:

await db.tx(async t => {
    await t.none('DELETE...');
    await t.any('UPDATE...');
});
vitaly-t
  • 24,279
  • 15
  • 116
  • 138
  • Ah, that solution is what I was approaching. Just wanted to make sure there wasn't a solution that would allow me to keep everything in one SQL file. I'll go ahead and implement it the way you outlined above. Thanks! :) – mbhuiyan Jul 25 '16 at 02:46
  • @mbhuiyan there can be a single-file a solution, but it would be way more awkward to implement and use, not really worth doing when a `pg-promise` transaction is so much easier to use. – vitaly-t Jul 25 '16 at 03:12
  • 1
    @vitaly-t Thank you so much for your work in making pg-promise, and thank you for answering so many postgres questions on SO and explaining how pg-promise can help solve the problem. – Will Ediger Jan 07 '20 at 19:38