Database: RDS PostgreSQL 12
I have a simeple proc that errors out. When it errors out I would like to log a record of it and then raise the exception. However I can only get it to do one of those things. I can either get it to log the error or I can raise the exception. I've tried both inserting the record directly from the exception block or by calling a proc that will insert the record into the table. How can I get it to do both? I'll post the code for the two blocks below. Any help would be appreciated!
CREATE OR REPLACE PROCEDURE error_test_prc ()
LANGUAGE plpgsql
AS $body$
DECLARE
var1 int;
BEGIN
SELECT 10/0 INTO var1;
EXCEPTION
WHEN OTHERS THEN
--INSERT INTO mdm_raw.raw_err_msg(msg_date, msg) VALUES(current_date, 'test');
CALL mdm_raw.error_logging_prc('this is a different proc test');
RAISE EXCEPTION 'Caught in Block 1 %', SQLERRM;
END;
$body$
CREATE OR REPLACE PROCEDURE error_logging_prc (msg_text text)
LANGUAGE plpgsql
AS $body$
DECLARE
var1 int;
BEGIN
INSERT INTO mdm_raw.raw_err_msg(msg_date, msg) VALUES(current_date, msg_text);
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Error Caught in error_logging_prc %', SQLERRM;
END;
$body$
The only known way to achieve that is to use the dblink extension to create a new connection from the database to itself. You can write your log message via dblink and commit it, then throw the error that will roll back the current transaction.
You can accomplish this by rollback on the current transaction, then insert and commit your log. See lines marked with --<<<<< in your procedure below. Another would be setting up a dblink.
CREATE OR REPLACE PROCEDURE error_test_prc ()
LANGUAGE plpgsql
AS $body$
DECLARE
var1 int;
BEGIN
SELECT 10/0 INTO var1;
EXCEPTION
WHEN OTHERS THEN
declare --<<<<<<
msg text --<<<<<<
begin --<<<<<<
msg = sqlerrm; --<<<<<<
rollback; --<<<<<<
CALL mdm_raw.error_logging_prc('this is a different proc test');
RAISE EXCEPTION 'Caught in Block 1 %',msg;
end ;
END;
$body$
CREATE OR REPLACE PROCEDURE error_logging_prc msg_text text)
LANGUAGE plpgsql
AS $body$
DECLARE
var1 int;
BEGIN
INSERT INTO mdm_raw.raw_err_msg(msg_date, msg) VALUES(current_date, msg_text);
commit; --<<<<<<
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Error Caught in error_logging_prc %', SQLERRM;
END;
$body$
Related
Here's below my procedure. Inside u find a loop. Target is if any of single loop execution will throw exception i don't want to stop the process and want to continue and commit each loop query which was executed succesfully. Therefore i placed exception catch inside loop. As u can see i also got a commit at the end and some begin/end blocks. My question is whether did i correctly or maybe i should put additional commit inside begin/end inside the loop (just after execute mysql;)? Thank you in advance.
CREATE OR REPLACE PROCEDURE myProc()
LANGUAGE plpgsql
AS $procedure$
declare
mysql text;
tb_name text;
myTables CURSOR for
SELECT table_name
FROM information_schema.tables
WHERE table_type='BASE TABLE'
AND table_schema='dist';
begin
begin
call DoSomeJob();
for tb in myTables loop
tb_name := tb;
begin
mysql := format('delete from %I where somecol=2', tb_name);
execute mysql;
exception
when others then
raise notice '% %', SQLERRM, SQLSTATE;
end ;
end loop;
call doOtherJob();
exception
when others then
raise notice 'The transaction is in an uncommittable state. '
'Transaction was rolled back';
raise notice '%: %', SQLSTATE, sqlerrm;
end ;
commit;
end;
$procedure$
;
UPDATE:
CREATE OR REPLACE PROCEDURE myProc()
LANGUAGE plpgsql
AS $procedure$
declare
mysql text;
tb_name text;
myTables CURSOR for
SELECT table_name
FROM information_schema.tables
WHERE table_type='BASE TABLE'
AND table_schema='dist';
begin
begin
call DoSomeJob();
exception
when others then
raise notice 'The transaction is in an uncommittable state. '
'Transaction was rolled back';
raise notice '%: %', SQLSTATE, sqlerrm;
end;
RAISE EXCEPTION 'ERROR test';
for tb in myTables loop
tb_name := tb;
begin
mysql := format('delete from %I where somecol=2', tb_name);
execute mysql;
exception
when others then
raise notice '% %', SQLERRM, SQLSTATE;
end ;
end loop;
begin
call doOtherJob();
exception
when others then
raise notice 'The transaction is in an uncommittable state. '
'Transaction was rolled back';
raise notice '%: %', SQLSTATE, sqlerrm;
end;
commit;
end;
$procedure$;
I explain you how to works PostgreSQL transactions in procedures.
Firstly
begin is a - "begin transaction";
end is a - "commit transaction"
And when you using transaction in another transaction (sub transactions) when your first level transaction rollbacked then all sub transactions also will be rollbacked.
For example:
begin --block call2
begin --block call1
call1;
exception
when others then
raise notice 'error call1'
end;
call2;
exception
when others then
raise notice 'error call2'
end;
Here when call2 failed then call1 also be failed. Because block call1 is in the block call2
And on your procedure - when doOtherJob() will be fail then all your inserted data will fail to. For solving this problem you can write your procedure as below:
CREATE OR REPLACE PROCEDURE myProc()
LANGUAGE plpgsql
AS $procedure$
declare
mysql text;
tb_name text;
myTables CURSOR for
SELECT table_name
FROM information_schema.tables
WHERE table_type='BASE TABLE'
AND table_schema='dist';
begin
call DoSomeJob();
for tb in myTables loop
tb_name := tb;
begin
mysql := format('delete from %I where somecol=2', tb_name);
execute mysql;
exception
when others then
raise notice '% %', SQLERRM, SQLSTATE;
end ;
end loop;
begin
call doOtherJob();
exception
when others then
raise notice 'The transaction is in an uncommittable state. '
'Transaction was rolled back';
raise notice '%: %', SQLSTATE, sqlerrm;
end;
commit;
end;
$procedure$;
You can set for block DoSomeJob() to the same logic.
When using procedures in PostgreSQL, especially about transactions, you need to know and understand some details well. I will now explain to you the important details and you will understand how it works.
begin - this is start transaction (always)
exception
end - this is not always commit-transaction, because if exception is created then end will be rollback-transaction if else end will be commit-transaction
And remember that: each block end affects only its own block begin
At the same time, pay attention to this:
During rollback process, all other begin-exception-end blocks which is under between the begin block and the exception block will be rollback.
Please execute this block and view result: select * from table test1
DO
$body$
begin
CREATE TABLE test1 (
id int4 NOT NULL,
CONSTRAINT test1_pk PRIMARY KEY (id)
);
begin -- block 1
insert into test1 (id) values (1);
exception
when others then
raise notice 'exception ';
end;
begin -- block 2
insert into test1 (id) values (error);
exception
when others then
raise notice 'exception ';
end;
begin -- block 3
insert into test1 (id) values (3);
exception
when others then
raise notice 'exception ';
end;
begin --block 4
begin -- block 5
insert into test1 (id) values (5);
exception
when others then
raise notice 'exception ';
end;
insert into test1 (id) values (error);
exception
when others then
raise notice 'exception ';
end;
END;
$body$
LANGUAGE 'plpgsql';
block 1 - success execute (success insert 1) - commit
block 2 - execute with error (no inserted record) - rollback
block 3 - execute success (success insert 3) - commit
block 5 - execute success (success insert 5) - commit
block 4 - execute with error (no inserted record) - rollback
Since block 5 is inside block 4 so block 5 already rollbacked
Result: select * from test1
1
3
I have created a procedure with INOUT refcursor parameter.
create or replace procedure test_rc(inout ref_cur refcursor)
as
$$
begin
if 1 = 2 then
open ref_cur for select 'one';
end if;
end;
$$
language plpgsql;
If the control doesn't reach the 'OPEN refcursor.........' statement during procedure execution, I always get the below error.
ERROR: cursor "r" does not exist SQL state: 34000
How can I catch this in an exception block to avoid getting error and return a NULL refcursor output instead? (Just like in Oracle PL/SQL).
I have tried using WHEN sqlstate '34000' in exception block but it did not catch this exception.
I'm trying to understand the exception handling in PL/pgSQL but ...
Case 1:
CREATE OR REPLACE PROCEDURE ins ()
AS $$ DECLARE
i INT;
BEGIN
insert into scott.emp (empno) values (9000);
commit;
i:=1/0;
EXCEPTION WHEN OTHERS THEN
--rollback;
END $$ LANGUAGE plpgsql;
call ins();
I would guess the line is inserted, but it is not
CASE 2:
CREATE OR REPLACE PROCEDURE ins ()
AS $$ DECLARE
i INT;
BEGIN
insert into scott.emp (empno) values (9000);
commit;
--i:=1/0;
EXCEPTION WHEN OTHERS THEN
--rollback;
END $$ LANGUAGE plpgsql;
call ins();
Now, there is no div by zero , but alos no line in the table
Case 3:
CREATE OR REPLACE PROCEDURE ins ()
AS $$ DECLARE
i INT;
BEGIN
insert into scott.emp (empno) values (9000);
commit;
--i:=1/0;
--EXCEPTION WHEN OTHERS THEN
--rollback;
END $$ LANGUAGE plpgsql;
call ins();
Now the line is inserted. It seems that the Problen is my Exception Block, but why?
Thanks in advance.
Marco
Silently throwing away your error message certainly does make debugging difficult.
CREATE OR REPLACE PROCEDURE ins ()
AS $$ DECLARE
i INT;
BEGIN
insert into scott.emp (empno) values (9000);
commit;
--i:=1/0;
EXCEPTION WHEN OTHERS THEN
raise notice E'%', SQLERRM;
END $$ LANGUAGE plpgsql;
Shows:
NOTICE: cannot commit while a subtransaction is active
So this due to the documented limitation:
A transaction cannot be ended inside a block with exception handlers.
I'm translating a Oracle trigger to Postgres; I have this so far translated in postgres
DROP TRIGGER IF EXISTS trg_test_biud ON mytable CASCADE;
CREATE OR REPLACE FUNCTION trigger_fct_trg_test_biud() RETURNS trigger AS $BODY$
DECLARE
id_ double precision := NULL;
hour_ varchar(10) := NULL;
BEGIN
/* INSERT */
IF TG_OP = 'INSERT' THEN
BEGIN
select nextval('myschema.id_audit_mytable_seq') into id_;
SELECT TO_CHAR(current_timestamp, 'HH24:MI:SS') INTO hour_;
INSERT INTO myschema.audit_mytable(id, id_mytable, user_name, event, myhour, hour, geometry)
VALUES (id_, NEW.code, NEW.user_name, 'INSERT', LOCALTIMESTAMP, hour_, NEW.GEOMETRY);
RETURN NEW;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION '%', 'Error when insert into audit_mytable: ' || sqlerrm USING ERRCODE = '-20000';
END;
END IF;
END
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_test_biud
BEFORE INSERT OR UPDATE OR DELETE ON myschema.mytable FOR EACH ROW
EXECUTE PROCEDURE trigger_fct_trg_test_biud();
When the exception is raised, I get this error:
ERROR: unrecognized exception condition «-20000»
SQL state: 42704
Does this has to do with the fact that in Oracle the 'custom error code' is a negative number? postgres does not recognize this? I checked this page, but it says nothing about negative numbers: https://www.postgresql.org/docs/current/static/errcodes-appendix.html
The Oracle number -20000 is not an SQLSTATE, but a proprietary error code.
You have to specify one of the 5-character SQLSTATEs defined in appendix A of the documentation.
I have table called employee with two columns, and have created two functions for
insertion and updation operations. These two function will be called through another
function which named as udf_3().
I want to do exception handling on the third function that is udf_3() which should
give me the details of which function has error.
--Table: employee
create table employee
(
id int,
name varchar(10)
);
--Function 1: udf_1() used for insertion.
create or replace function udf_1()
returns void as
$body$
begin
insert into employee values(1,'Mak');
end;
$body$
language plpgsql;
--Function 2: udf_2() used for updation.
create or replace function udf_2()
returns void as
$body$
begin
update employee
set a_id = 99
where name = 'Mak';
end;
$body$
language plpgsql;
--Function 3: udf_3() used to call all above function.
create or replace function udf_3()
returns int as
$body$
begin
perform udf_1();
perform udf_2();
return 0;
exception
when others then
RAISE INFO 'Error Name:%',SQLERRM;
RAISE INFO 'Error State:%', SQLSTATE;
return -1;
end;
$body$
language plpgsql;
--Function Calling:
select * from udf_3();
Exception:
INFO: Error Name:column "a_id" of relation "employee" does not exist
INFO: Error State:42703
Problem: I am able to get the exception BUT not able to get from which function i got exception.
According to the documentation
Within an exception handler, one may also retrieve information about the current exception by using the GET STACKED DIAGNOSTICS command
https://www.postgresql.org/docs/9.5/static/plpgsql-control-structures.html#PLPGSQL-EXCEPTION-DIAGNOSTICS
Example:
create or replace function udf_3()
returns int as
$body$
declare
err_context text;
begin
perform udf_1();
perform udf_2();
return 0;
exception
when others then
GET STACKED DIAGNOSTICS err_context = PG_EXCEPTION_CONTEXT;
RAISE INFO 'Error Name:%',SQLERRM;
RAISE INFO 'Error State:%', SQLSTATE;
RAISE INFO 'Error Context:%', err_context;
return -1;
end;
$body$
language plpgsql;
will display the following:
INFO: Error Context:SQL: "SELECT udf_1()"
But this is just a textual representation of the error. Your logic should not rely on it. It is better to use custom error codes to handle exception logic (and raise meaningful exceptions in your functions that you can catch and handle later on).
UPDATE:
Another solution is to separate your code in different blocks for which you can catch exceptions separately. In this case you know from which block the exception was raised:
DO $$
BEGIN
-- Block 1
BEGIN
-- any code that might raise an exception
RAISE EXCEPTION 'Exception 1'; -- for example
EXCEPTION
WHEN others THEN
RAISE INFO 'Caught in Block 1';
END;
-- Block 2
BEGIN
-- any code that might raise an exception
RAISE EXCEPTION 'Exception 2'; -- for example
EXCEPTION
WHEN others THEN
RAISE INFO 'Caught in Block 2';
END;
END $$