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.
Related
I am creating a function that allow me to conditionally update specific columns in a table. However, I get an error indicating that there is a syntax error at or near "IF" when I try to run the following code. I'm a bit new to Postgres so it's quite possible. I can't understand some concept/syntax thing in Postgres. Can someone help me by pointing out the mistake I must be making?
CREATE OR REPLACE FUNCTION profiles.do_something(
p_id UUID,
p_condition1 BOOLEAN,
p_condition2 BOOLEAN,
p_condition3 BOOLEAN
)
RETURNS void AS $$
BEGIN
IF p_condition1 IS TRUE THEN
UPDATE tablename SET column1 = null WHERE member_id = p_id;
END IF;
IF p_condition2 IS TRUE THEN
UPDATE tablename SET column2 = null WHERE member_id = p_id;
END IF;
IF p_condition3 IS TRUE THEN
UPDATE tablename SET column3 = null WHERE member_id = p_id;
END IF;
END;
$$ LANGUAGE 'sql';
tl;dr $$ LANGUAGE 'plpgsql'
$$ LANGUAGE 'sql';
^^^^^
You're tell it to parse the body of the function as sql. In SQL, begin is a statement which starts a transaction.
create or replace function test1()
returns void
language sql
as $$
-- In SQL, begin starts a transaction.
-- note the ; to end the statement.
begin;
-- Do some valid SQL.
select 1;
-- In SQL, end ends the transaction.
end;
$$;
In SQL you wrote begin if ... which is a syntax error.
The language you're using is plpgsql. In plpgsql, begin is a keyword which starts a block.
create or replace function test1()
returns void
language plpgsql
as $$
-- In PL/pgSQL, begin starts a block
-- note the lack of ;
begin
-- Do some valid SQL.
select 1;
-- In PL/pgSQL, end ends the block
end;
$$;
CREATE OR REPLACE FUNCTION ddl_command_test()
RETURNS event_trigger
AS $$
DECLARE
obj record;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands()
LOOP
RAISE NOTICE '% commnad on oid: %',
tg_tag,
obj.objid;
RAISE NOTICE 'triggered';
END LOOP;
END; $$ LANGUAGE plpgsql;
CREATE EVENT TRIGGER test_ddl ON ddl_command_end
EXECUTE FUNCTION ddl_command_test();
While pg_event_trigger_ddl_commands() function returns info for table creation:
CREATE TABLE test_table(
col int
);
It not prints notification message when table is dropped:
DROP TABLE test_table;
Don't get why, because event-trigger-matrix shows that ddl_command_end includes DROP TABLE command also.
Although the documentation event-trigger-matrix says that ddl_command_end can be used for DROP statements, I also struggled with that issue in the past.
Thereby, I found this workaround, which involves creating a specific function that fetches FROM pg_event_trigger_dropped_objects(), to notify when the DROP statement is used.
CREATE OR REPLACE FUNCTION ddl_drop_command_test()
RETURNS event_trigger
AS $$
DECLARE
obj record;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
LOOP
RAISE NOTICE '% commnad on oid: %',
tg_tag,
obj.objid;
RAISE NOTICE 'triggered';
END LOOP;
END; $$ LANGUAGE plpgsql;
Further, you need to use ON sql_drop to create your event trigger. The WHEN TAG can be incremented with DROP SCHEMA and other Postgres objects.
CREATE EVENT TRIGGER drop_test ON sql_drop
WHEN TAG IN ('DROP TABLE')
EXECUTE PROCEDURE ddl_drop_command_test();
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$
The below function neither returning the value nor raising the exception.
create or replace function get_custid(p_customerNum varchar2)
RETURNS text AS $$
DECLARE
cust_id customer.customer_num%TYPE;
begin
raise notice '%', message_text;
select customer_num into cust_id
from customer
where customer_num = p_customerNum;
return cust_id;
exception
when OTHERS then
raise notice '%', message_text;
raise;
end $$ language plpgsql;
select get_custid('Ab12345') from dual;
-- the customer number is existed but not returning any rows.
select get_custid('DDDDDDD') from dual;
-- the customer number is not existed but not going to exception block
I think that is you really use postgresql this code is more likely what you need (or at list it's running...)
create table customer (cust_id int, customer_num varchar);
insert into customer values (1, 'Ab12345');
drop function get_custid(varchar);
create or replace function get_custid(p_customerNum varchar)
RETURNS int AS $$
DECLARE
out_cust_id int;
begin
--raise notice '%', message_text;
select cust_id into out_cust_id
from customer
where customer_num = p_customerNum;
if out_cust_id is null
then raise exception 'your exception';
end if;
return out_cust_id;
end $$ language plpgsql;
select get_custid('Ab12345');
In PL/pgSQL, SELECT INTO only throws an exception on the wrong number of rows if STRICT is specified.
create or replace function get_custid(p_customerNum varchar)
RETURNS text AS $$
DECLARE
cust_id customer.customer_num%TYPE;
begin
raise notice '%', 'message_text';
select customer_num into strict cust_id
from customer
where customer_num = p_customerNum;
return cust_id;
exception
when OTHERS then
raise notice '%', 'message_text';
raise;
end $$ language plpgsql;
This function compiled successfully:
CREATE OR REPLACE FUNCTION FieldValidations1(tbl_name varchar(35),col_name varchar(25), error_flag varchar(3))
RETURNS void AS $$
declare
cust_rec RECORD;
BEGIN
execute format($sel$
FOR cust_rec IN SELECT %I FROM %s
LOOP
RAISE NOTICE 'inside loop';
END LOOP;
$sel$,
col_name,tbl_name);
END;
$$ LANGUAGE plpgsql;
But while calling the function,
select FieldValidations1('raw_tbl1','col1','gg');
the error appears like this
ERROR: syntax error at or near "FOR"
LINE 3: FOR cust_rec IN SELECT col1 FROM raw_tbl1
^
QUERY:
FOR cust_rec IN SELECT col1 FROM raw_tbl1
LOOP
RAISE NOTICE 'inside loop';
END LOOP;
CONTEXT: PL/pgSQL function "fieldvalidations1" line 6 at EXECUTE statement
Can anyone explain what's wrong?
You must be aware that plpgsql functions are only tested on a superficial syntactical level in CREATE FUNCTION. It does not find all possible errors, it does not try to evaluate functions.
Would work like this:
CREATE OR REPLACE FUNCTION field_validations2(tbl_name text
, col_name text, error_flag text)
RETURNS void AS
$func$
DECLARE
cust_rec RECORD;
BEGIN
FOR cust_rec IN
EXECUTE format('SELECT %I FROM %I', col_name, tbl_name)
LOOP
RAISE NOTICE 'inside loop';
END LOOP;
END
$func$ LANGUAGE plpgsql;
The proper syntax for a FOR-IN-EXECUTE statement can be found in the manual.
Also fixed a couple of other things. text instead of varchar(n) is just a friendly advice.
It is not possible to execute plpgsql. It must be plain SQL.