Postgres Exception handling in stored procedure to continue the process - postgresql

Database is postgressql.
I want to call five stored procedure in on stored procedure that is master_call_all() and need to handle exception after every call. So if any error occur it raise the exception and not terminate the process.
Let say error occur in 3rd call scratch.sp_insert3() then after raising error next stored procedure will call. This thing I want to achieve after every call.
create or replace procedure scratch.master_call_all()
LANGUAGE plpgsql
AS $$
DECLARE
BEGIN
call scratch.sp_insert1();
call scratch.sp_insert2();
call scratch.sp_insert3();
call scratch.sp_insert4();
call scratch.sp_insert5();
commit;
END;
$$;

First off: In general this is an extremely bad plan. It is the anti-thesis of creating a Transaction (begin ... end).
Transactions are a fundamental concept of all database systems. The essential point of a transaction is that it bundles multiple steps
into a single, all-or-nothing operation. The intermediate states
between the steps are not visible to other concurrent transactions,
and if some failure occurs that prevents the transaction from
completing, then none of the steps affect the database at all.
Ref: Database Transactions
Now with that out of the way, what you are asking can be done, but it is not done cleanly. A code block in Postgres contains 3 sections declaration, execution, exception with declaration and exception optional. Further a block can be nested inside another and the nested block retains all 3 sections. So what you need is to place each ca;; statement into a nested block. Like:
create or replace procedure master_call_all()
language plpgsql
as $$
declare
begin
begin
call sp_insert1();
exception
when others then
raise notice 'Exception occurred calling sp_insert1()';
end ;
begin
call sp_insert2();
exception
when others then
raise notice 'Exception occurred calling sp_insert2()';
end ;
begin
call sp_insert3();
exception
when others then
raise notice 'Exception occurred calling sp_insert3()';
end ;
begin
call sp_insert4();
exception
when others then
raise notice 'Exception occured calling sp_insert4()';
end ;
begin
call sp_insert5();
exception
when others then
raise notice 'Exception occured calling sp_insert5()';
end ;
commit;
end;
$$;
See demo here. Unfortunately db<>fiddle does not show the messages from raise notice .... Clearly in the demo sp_insert1, and sp_insert3 will raise an exception. (To see the messages run in your environment.)

Related

Trouble with commit in a postgres procedure [duplicate]

I have some procedure which when i execute in dbeaver works fine without issue, however when i call it from outside program i am getting error below. I do not want to copy/paste full procedure here because it's pretty big and it works in db tool. I just copy/paste top and bottom. What could be the cause of it?
Procedure:
CREATE OR REPLACE PROCEDURE MyProcedure(lot of args..)
LANGUAGE plpgsql
AS $procedure$
DECLARE
.....
.....
COMMIT;
END;
$procedure$
;
Error:
ERROR: invalid transaction termination
Where: PL/pgSQL function MyFunction line 185 at COMMIT Call getNextException to see other errors in the batch. Line 185 at COMMIT Call getNextException to see other errors in the batch.
The documentation says:
Transaction control is only possible in CALL or DO invocations from the top level or nested CALL or DO invocations without any other intervening command. For example, if the call stack is CALL proc1() → CALL proc2() → CALL proc3(), then the second and third procedures can perform transaction control actions. But if the call stack is CALL proc1() → SELECT func2() → CALL proc3(), then the last procedure cannot do transaction control, because of the SELECT in between.
There are some other, undocumented, restrictions:
You cannot start a transaction explicitly with BEGIN and commit it inside a transaction. So the following will fail:
START TRANSACTION;
CALL procedure_with_commit();
This may be improved in future releases.
All procedures in the call stack must be written in PL/pgSQL:
CREATE PROCEDURE b() LANGUAGE plpgsql
AS 'BEGIN PERFORM 42; COMMIT; END;';
CREATE PROCEDURE a() LANGUAGE sql
AS 'CALL b()';
CALL a();
ERROR: invalid transaction termination
CONTEXT: PL/pgSQL function b() line 1 at COMMIT
SQL function "a" statement 1
As it is, transaction control inside PostgreSQL procedures is somewhat limited.
If you violate any of these rules, you will get the error message you describe in your question. You will probably have to handle transactions in the application rather than in the procedure — perhaps splitting the procedure into smaller parts makes this possible.

Ignore an error that occurs in a redshift stored procedure

I need to ignore an error when running a procedure, but the error still seems to propagate. Is it possible to just swallow it somehow?
CREATE OR REPLACE PROCEDURE myproc()
AS $$
BEGIN
EXECUTE 'bad_statement;';
EXCEPTION WHEN OTHERS THEN
NULL;
END;
$$ LANGUAGE plpgsql;
Then
call myproc();
will result in:
[2021-01-06 17:08:42] [42601][500310] Amazon Invalid operation: syntax error at or near "bad_statement";
Tested it in postgres and it ignores the error correctly.
Unfortunately I don't think there is a way to do this. Amazon's documentation states
In an Amazon Redshift stored procedure, the only supported handler_statement is RAISE. Any error encountered during the execution automatically ends the entire stored procedure call and rolls back the transaction. This occurs because subtransactions are not supported.
If an error occurs in the exception handling block, it is propagated out and can be caught by an outer exception handling block, if one exists.
stored-procedure-trapping-errors

Invalid transaction termination

I have some procedure which when i execute in dbeaver works fine without issue, however when i call it from outside program i am getting error below. I do not want to copy/paste full procedure here because it's pretty big and it works in db tool. I just copy/paste top and bottom. What could be the cause of it?
Procedure:
CREATE OR REPLACE PROCEDURE MyProcedure(lot of args..)
LANGUAGE plpgsql
AS $procedure$
DECLARE
.....
.....
COMMIT;
END;
$procedure$
;
Error:
ERROR: invalid transaction termination
Where: PL/pgSQL function MyFunction line 185 at COMMIT Call getNextException to see other errors in the batch. Line 185 at COMMIT Call getNextException to see other errors in the batch.
The documentation says:
Transaction control is only possible in CALL or DO invocations from the top level or nested CALL or DO invocations without any other intervening command. For example, if the call stack is CALL proc1() → CALL proc2() → CALL proc3(), then the second and third procedures can perform transaction control actions. But if the call stack is CALL proc1() → SELECT func2() → CALL proc3(), then the last procedure cannot do transaction control, because of the SELECT in between.
There are some other, undocumented, restrictions:
You cannot start a transaction explicitly with BEGIN and commit it inside a transaction. So the following will fail:
START TRANSACTION;
CALL procedure_with_commit();
This may be improved in future releases.
All procedures in the call stack must be written in PL/pgSQL:
CREATE PROCEDURE b() LANGUAGE plpgsql
AS 'BEGIN PERFORM 42; COMMIT; END;';
CREATE PROCEDURE a() LANGUAGE sql
AS 'CALL b()';
CALL a();
ERROR: invalid transaction termination
CONTEXT: PL/pgSQL function b() line 1 at COMMIT
SQL function "a" statement 1
As it is, transaction control inside PostgreSQL procedures is somewhat limited.
If you violate any of these rules, you will get the error message you describe in your question. You will probably have to handle transactions in the application rather than in the procedure — perhaps splitting the procedure into smaller parts makes this possible.

Commit the transaction after exception is raised

I tried looking up for the solution but failed to find one. I understand that postgres rollbacks all the transactions once an exception is raised. But my requirement is to log a certain exception into a database table.
Following is a sample code:
CREATE OR REPLACE FUNCTION fn_test(
in_var numeric DEFAULT NULL::numeric)
RETURNS numeric AS
$BODY$
DECLARE
BEGIN
IF in_var=0
THEN
RAISE EXCEPTION using errcode='INELG';
ELSE IF in_var=1
THEN
RAISE EXCEPTION using errcode='INVAL';
ELSE
RETURN in_var;
END IF;
begin
EXCEPTION
WHEN sqlstate 'INELG'
THEN
INSERT INTO LOG_TBL(in_par,error_reason)
VALUES(in_var,'VALUE INELGIBLE');
RAISE EXCEPTION 'Unable to Process: Parameter Not Eligible';
WHEN sqlstate 'INVAL'
THEN
INSERT INTO LOG_TBL(in_par,error_reason)
VALUES(in_var,'VALUE INValid');
RAISE EXCEPTION 'Unable to Process: Parameter Invalid';
end;
END;
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER
COST 100;
The problem I face is as soon as an exception is raised the records in LOG_TBL in the exception section gets rolled back. Is there an alternative solution where I can insert the record into database table as well as raise an application error?
In a simple way - you can't. Once you are inside a function, that's it.
You either have to do this in the client layer or loop back in on another connection to log the error. Traditionally you would loop back in via dblink, but it might be possible to use Foreign Data Wrappers too - I have to admit I'm not sure.
Edit: Nope, it looks like postgres_fdw syncs the transactions between local and remote. Not helpful in this case. Looks like dblink is the obvious choice.

trigger to silently preempt insertion in a table

Is it possible for a trigger to be defined in such a way that the row that was to be inserted is not inserted, without raising an exception? My use case is that I want to simplify the exception handling for the client library: the client library will just execute a statement to insert a row in a table and I was hoping that the trigger could be defined, more or less using the below syntax:
CREATE TRIGGER control_tr AFTER INSERT ON tableFoo
FOR EACH ROW
EXECUTE PROCEDURE control_tr_fun();
CREATE OR REPLACE FUNCTION control_tr_fun() RETURNS TRIGGER AS $$
BEGIN
IF (NOT condition_is_met(NEW.a, NEW.b, NEW.c)) THEN
DO NOTHING INSTEAD OF INSERT // syntax I am hoping for instead of RAISE EXCEPTION
ELSE
RETURN NEW;
END IF;
END
$$ LANGUAGE plpgsql;
I appreciate that I can ask the client library to call a PL/pgSQL function or make a trigger that RAISEs an exception and ask the client library to catch the exception (if raised) and just ignore it, but I am looking for a way to implement this as transparently for the client as possible.
If you RETURN NULL then nothing will happen, the INSERT will fail silently. But you need to define the trigger as BEFORE INSERT, and not AFTER INSERT as it is in your example.
If you RAISE EXCEPTION, then the entire transaction will fail.
You can also RAISE NOTICE without failing the transaction and catch it in the client library, but only if it was the last notice produced. You cannot stack notices.