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 $$
Related
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$
I am trying to return a single row of a table in a function and it is important that if there isn't any row or more then one there is an exception raised. I am using SELECT * INTO STRICT for that reason. However, I can't seem to find the correct way to return. Was wondering if someone knew the correct way of returning the single row?
I know I can separate the check and get it work but was curious if there was a way to get this to work.
CREATE OR REPLACE FUNCTION GameInfo.getAllPlayerInfo(
playerID GameInfo.Player.PID%Type)
RETURNS TABLE (PID VARCHAR,Name VARCHAR, Email VARCHAR,Password VARCHAR) AS
$$
DECLARE
found_player GameInfo.Player%ROWTYPE;
BEGIN
SELECT * INTO STRICT found_player FROM GameInfo.Player WHERE Player.PID =
$1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'PID % not found';
WHEN TOO_MANY_ROWS THEN
RAISE EXCEPTION 'PID % not unique';
RETURN found_player;
END;
$$ LANGUAGE plpgsql
STABLE
SECURITY DEFINER;
Your function is declared as returns table, so in PL/pgSQL you need to use return next... to return a row.
That return statements needs to be moved before the exception handling. Currently your function does not return anything, if a row is found.
You can also simplify the function declaration by using returns setof so you don't need to specify all columns.
CREATE OR REPLACE FUNCTION GameInfo.getAllPlayerInfo(playerID GameInfo.Player.PID%Type)
RETURNS setof gameinfo.player
AS $$
DECLARE
found_player GameInfo.Player%ROWTYPE;
BEGIN
SELECT *
INTO STRICT found_player
FROM GameInfo.Player
WHERE Player.PID = playerid;
RETURN NEXT found_player; --<< here
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'PID % not found';
WHEN TOO_MANY_ROWS THEN
RAISE EXCEPTION 'PID % not unique';
END;
$$
LANGUAGE plpgsql
STABLE
SECURITY DEFINER;
Note that set returning functions need to be used in the FROM clause:
select *
from gameinfo.getallplayerinfo(1);
I need to port over some Oracle PL/SQL code to Postgres. This is my first time working with Postgres.
In Oracle, with regards to exceptions, I can have this:
IF v_customer_id IS NULL OR v_email IS NULL THEN
RAISE invalid_paramters;
END IF;
How is that done in Postgres? Basically validating input, and if anything fails validation, call a custom handler to perform whatever actions. From what I have read, Postgres does not support custom named exceptions.
Thanks for your time.
You could use RAISE with a custom message and a specific sqlstate constant:
--Anonymous block for testing purposes
DO $$
BEGIN
RAISE invalid_parameter_value USING MESSAGE = 'Invalid customer or email';
END $$;
Or you could simply raise a generic exception:
DO $$
BEGIN
RAISE EXCEPTION 'A generic exception (P0001)';
END $$;
You could also handle a exception:
DO $$
BEGIN
--This will raise a division by zero
PERFORM 0 / 0;
--You can catch a exception with a EXCEPTION block
EXCEPTION
WHEN division_by_zero THEN
RAISE INFO 'Division by zero catched';
WHEN raise_exception THEN
RAISE INFO 'Another error catched...';
END $$;
And get more detailed information about a exception:
DO $$
DECLARE
error_msg text;
BEGIN
--This will raise a division by zero
PERFORM 0 / 0;
--You can get more about error with GET STACKED DIAGNOSTICS
EXCEPTION
--Tip: OTHERS keyword will catch any exception
WHEN OTHERS THEN
GET STACKED DIAGNOSTICS error_msg = MESSAGE_TEXT;
RAISE EXCEPTION 'My custom exception: %', error_msg;
END $$;
I'd suggest you to take a look on sqlstates and control structures for more about error handling in PostgreSQL.
do $$
declare
v int := 1;
begin
begin
if v < 2 then -- Validation here
raise sqlstate 'A0001'; -- custom SQL state, any five upper case letters and/or digits
end if;
exception -- Catch exception in the nested BEGIN ... END block
when sqlstate 'A0001' then
raise notice 'Here is my exception handler';
v := 2;
end;
raise notice 'Continue here with value %', v; -- Reports that the v = 2
end $$;
Instead of capturing it in the main exception block, you can add a nested begin...end block in the place of RAISE EXCEPTION part and raise an exception inside and capture it in the others exception block inside the nested block with a RETURN keyword.
do
$$
begin
raise notice 'line 1';
begin
raise exception 'raising exception';
exception
when others then
raise notice 'captured in nested block';
end;
return;
raise notice 'check if it continues';
exception
when others then
raise notice 'main block others';
end;
$$
language plpgsql;
I use the RAISE EXCEPTION '...' USING ERRCODE='....' quite a lot in my code, as I can use the error code in my C# code. However, I would like to use it now in my plpgsql code, like this:
BEGIN
...
RAISE EXCEPTION 'Something is wrong' USING ERRCODE='S0001';
EXCEPTION WHEN 'S0001' THEN
-- Handle code S0001
END;
But that doesn't work. How can I catch and process my own thrown exceptions in plpgsql ?
Your exception handling clause should look like this:
EXCEPTION
WHEN SQLSTATE 'S0001'
THEN
...
END;
Use sqlstate, e.g.:
drop function if exists test();
create or replace function test()
returns int language plpgsql as $$
begin
raise exception using errcode = 50001;
return 0;
exception when sqlstate '50001' then
return sqlstate;
end $$;
select test();
test
-------
50001
(1 row)
CREATE OR REPLACE FUNCTION msgfailerror() RETURNS trigger AS
' BEGIN
IF NEW.noces< new.first_column THEN
RAISE EXCEPTION 'cannot have a negative salary';
END IF;
return new;
END' LANGUAGE plpgsql
Trigger
create trigger msgfail before insert on first for each row
execute procedure msgfailerror()
Giving error:
syntax error at or near "cannot" LINE 5: RAISE
EXCEPTION 'cannot have a negative ...
I have almost one validation for each field of row. I want trigger to check all validations while insertion is being done and, raise error log afterwards once for all. Should I use raise exception on raise notice ?
For example:
Insert into first (first_column, noces,dob) values ('4545','75','545')
I am checking noces is less than first_column, for the same row i want to check if dob > 80 and if first_column is integer and raise error for all validations. Thanks in advance
The quoting is wrong. It's easier to use dollar quotes $$:
CREATE OR REPLACE FUNCTION msgfailerror()
RETURNS trigger AS
$$
BEGIN
IF NEW.noces< new.first_column THEN
RAISE EXCEPTION 'cannot have a negative salary';
END IF;
return new;
END;
$$
LANGUAGE plpgsql;
But on the other hand, what's wrong with a check constraint?
there is nothing wrong with you the only thing is using quotes
please change :
RAISE EXCEPTION 'cannot have a negative salary';
to:
RAISE EXCEPTION ''cannot have a negative salary'';
'' is different from "
'' = two single quotes
I agree with Frank that you could better use constraints, but you call it validation. Validation is typically done before insertion takes place. If you would like to validate insertions, you could use functions instead of triggers or constraints.
When you would write functions is the answer to your question to raise exceptions or notices that as long as there has been no write action a notice would suffice (together with leaving the function). Once there has been a write to the database, do you have to use exceptions as they perform a rollback.
Like this:
CREATE OR REPLACE FUNCTION field_validate(p_int int) RETURNS boolean AS $$
DECLARE
i_id int;
BEGIN
if p_int > 10 then
raise notice 'should be smaller then 10';
return false;
end if;
insert into tbl_numbers(firstfield) values(p_int) returning id in i_id;
insert into tbl_fake(nofield) values(i_id);
return true;
EXCEPTION
WHEN raise exception THEN
return false;
END;
$$ LANGUAGE plpgsql;