23

I'm writing a script for PostgreSQL and since I want it to be executed atomically, I'm wrapping it inside a transaction.
I expected the script to look something like this:

BEGIN
-- 1) Execute some valid actions;
-- 2) Execute some action that causes an error.
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
END; -- A.k.a. COMMIT;

However, in this case pgAdmin warns me about a syntax error right after the initial BEGIN. If I terminate the command there by appending a semicolon like so: BEGIN; it instead informs me about error near EXCEPTION.
I realize that perhaps I'm mixing up syntax for control structures and transactions, however I couldn't find any mention of how to roll back a failed transaction in the docs (nor in SO for that matter).

I also considered that perhaps the transaction is rolled back automatically on error, but it doesn't seem to be the case since the following script:

BEGIN;
-- 1) Execute some valid actions;
-- 2) Execute some action that causes an error.
COMMIT;

warns me that: ERROR: current transaction is aborted, commands ignored until end of transaction block and I have to then manually ROLLBACK; the transaction.

It seems I'm missing something fundamental here, but what?

EDIT:
I tried using DO as well like so:

DO $$
BEGIN
-- 1) Execute some valid actions;
-- 2) Execute some action that causes an error.
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
END; $$

pgAdmin hits me back with a: ERROR: cannot begin/end transactions in PL/pgSQL. HINT: Use a BEGIN block with an EXCEPTION clause instead. which confuses me to no end, because that is exactly what I am (I think) doing.

POST-ACCEPT EDIT: Regarding Laurenz's comment: "Your SQL script would contain a COMMIT. That ends the transaction and rolls it back." - this is not the behavior that I observe. Please consider the following example (which is just a concrete version of an example I already provided in my original question):

BEGIN;

-- Just a simple, self-referencing table.
CREATE TABLE "Dummy" (
    "Id" INT GENERATED ALWAYS AS IDENTITY,
    "ParentId" INT NULL,
    CONSTRAINT "PK_Dummy" PRIMARY KEY ("Id"),
    CONSTRAINT "FK_Dummy_Dummy" FOREIGN KEY ("ParentId") REFERENCES "Dummy" ("Id")
);

-- Foreign key violation terminates the transaction.
INSERT INTO "Dummy" ("ParentId")
VALUES (99);

COMMIT;

When I execute the script above, I'm greeted with: ERROR: insert or update on table "Dummy" violates foreign key constraint "FK_Dummy_Dummy". DETAIL: Key (ParentId)=(99) is not present in table "Dummy". which is as expected. However, if I then try to check whether my Dummy table was created or rolled back like so:

SELECT EXISTS (
    SELECT FROM information_schema."tables"
    WHERE "table_name" = 'Dummy');

instead of a simple false, I get the same error that I already mentioned twice: ERROR: current transaction is aborted, commands ignored until end of transaction block. Then I have to manually terminate the transaction via issuing ROLLBACK;.

So to me it seems that either the comment mentioned above is false or at least I'm heavily misinterpreting something here.

6
  • EXCEPTION can only be used in PL/pgSQL, not in SQL If you want to use that, you need to use a do block Commented Aug 19, 2020 at 15:10
  • @a_horse_with_no_name I added an edit regarding your comment if you care to take a look. Commented Aug 19, 2020 at 15:26
  • @a_horse_with_no_name also I wouldn't say I want to use EXCEPTION. What I want is to roll back the transaction on an error. Perhaps there are other ways around that that I'm missing? Commented Aug 19, 2020 at 15:30
  • I see that this question has attracted some interest over time. I actually have been lucky enough to (with reasonably high confidence) get to the bottom of this. What I have had less luck with though, is to find myself some time to muster up a proper, detailed-enough answer. Will do my best to get to it in the next 2-3 months (so until August 1st 2021). Commented May 1, 2021 at 13:20
  • I fully understand your question as I'm experiencing this problem as well. This doesn't work: dbfiddle. But this works: dbfiddle. Although I would need to test this on a real PostgreSQL setup to confirm. My goal is to write an API without using ORMs and I wouldn't like to have to issue a 2nd query to rollback the 1st one if needed. Commented Aug 3, 2021 at 18:08

3 Answers 3

18

You cannot use ROLLBACK in PL/pgSQL, except in certain limited cases inside procedures.

You don't need to explicitly roll back in your PL/pgSQL code. Just let the exception propagate out of the PL/pgSQL code, and it will cause an error, which will cause the whole transaction to be rolled back.

Your comments suggest that this code is called from an SQL script. Then the solution would be to have a COMMIT in that SQL script at some place after the PL/pgSQL code. That would end the transaction and roll it back.

Sign up to request clarification or add additional context in comments.

14 Comments

Okay, but if that is the case then why do I (and I mention this in my question) get: ERROR: current transaction is aborted, commands ignored until end of transaction block and have to manually ROLLBACK the transaction to be able to execute any further commands?
@Marchyello - Postgres run ROLLBACK implicitly when you use EXCEPTION block.
@LaurenzAlbe Sorry, but the main issue is still unclear to me - how do I rollback a transaction as soon as the first error was encountered? You say that I need to let the exception propogate outside of my PL/pgSQL code. But propogate to where? Do I need an exception catching wrapper around my transaction? To me that doesn't make sense, because that would mean I'm already outside the scope of the transaction, but perhaps I'm wrong in this assumption. If I may add I find it odd that such a (imo) common scenario isn't documented and cannot be explained with a simple example.
You cannot end the transaction in the PL/pgSQL code. Your PL/pgSQL code is called from somewhere. That's the place where you need to perform ROLLBACK.
@LaurenzAlbe "[..] That ends the transaction and rolls it back" - unfortunately this is not what I see. If you still have some time and patience, take a look at my latest edit, where I provide a concrete example of this contradiction.
|
0

I had this error too when I tried to run my script after it raised an exception:

ERROR: current transaction is aborted, commands ignored until end of transaction block

To fix it, I simply added `ROLLBACK` at the beginning of my script, like this:

ROLLBACK; -- fix "current transaction is aborted, commands ignored until end of transaction block"

BEGIN;
DO
$$
    DECLARE
        v_nb INTEGER := 1;
    BEGIN
        RAISE NOTICE '0 + % = %', v_nb, v_nb;
        RAISE EXCEPTION 'trigger an exception for fun';
    END
$$;

COMMIT;

Comments

-1

I think you must be using an older version, as the exact code from your question works without error for me:

(The above is with PostgreSQL 13.1, and pgAdmin 4.28.)

It also works fine for me, without the exception block:

As per this comment, you can remove the exception block within a function, and if an error occurs, the transaction run within it will automatically be rolled back. That appears to be the case, from my limited testing.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.