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.
Related
When I try to run a PostgreSQL stored procedure including the "STRICT" option and a record variable (rec) as I try to save (run the create statement) I get the following error:
ERROR: syntax error at or near "rec"
LINE 14: INTO STRICT rec
I' am following the guidelines stated on the official documentation https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-SQL-ONEROW
where the only reference missing is that this can or can't be used inside a function or a stored procedure. I guess there should be a reasonable answer to this issue I couldn't found. I was able to succeed running an equivalent code inside a Do block, so it seams there is some incompatibility declaring a record type variable and functions.
This is the function code I was running:
CREATE OR REPLACE FUNCTION test_nodata()
RETURNS TABLE(id int, active boolean)
LANGUAGE plpgsql
AS $$
DECLARE
rec record;
tab VARCHAR ='text_maintenance_news';
BEGIN
RETURN QUERY
SELECT tm.id, tm.active
INTO STRICT rec
FROM text_maintenance_news tm
WHERE tm.active
LIMIT 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE 'Query from % has no data', tab;
END;
$$
As mentioned at the official documents, postgreSQL doesn't consider no data as an error but as a warning that the programmer must handle. Main reason to include the STRICT option.
Do block code (It works!) and result:
DO
LANGUAGE plpgsql
$$
DECLARE
rec record;
tab varchar = 'text_maintenance_news';
BEGIN
SELECT *
INTO STRICT rec
FROM text_maintenance_news
WHERE active
LIMIT 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE 'Query from % return no data', tab;
END;
$$
ERROR: Query from text_maintenance_news return no data
CONTEXT: PL/pgSQL function inline_code_block line 13 at RAISE
SQL state: P0001
I have this function where it looks up all the users and if any one does not exists do not create the function. What is wrong?
RETURNS event_trigger AS $$
DECLARE
audit_query TEXT;
r RECORD;
BEGIN
IF tg_tag IN ('CREATE TABLE', 'CREATE TABLE AS')
THEN
IF EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'test' or rolname = 'testa')
THEN
FOR r IN
SELECT * FROM pg_event_trigger_ddl_commands() AS ddl WHERE ddl.schema_name IN ('testb','testc','testd')
LOOP
INSERT INTO user_monitor.ddl_history (ddl_date, ddl_tag, object_name) VALUES (statement_timestamp(), tg_tag, r.object_identity);
END LOOP;
else
RAISE EXCEPTION 'Not all users found';
END IF;
END;
$$ LANGUAGE plpgsql
SQL Error [42601]: ERROR: syntax error at or near ";"
SQL Error [42601]: ERROR: missing "THEN" at end of SQL expression
Position: 286
An excerpt from the manual:
Event triggers are created using the command CREATE EVENT TRIGGER. In order to create an event trigger, you must first create a function with the special return type event_trigger.
This function need not (and may not) return a value; the return type serves merely as a signal that the function is to be invoked as an event trigger.
Emphasis on the second sentence.
You should probably replace your RETURN with RAISE EXCEPTION instead.
https://www.postgresql.org/docs/current/event-trigger-table-rewrite-example.html
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$
Is it possible to cancel previous operations in a user defined function?
For example:
CREATE OR REPLACE FUNCTION transact_test () RETURNS BOOLEAN
AS $$
BEGIN
UPDATE table1 SET ...
UPDATE table2 SET ...
IF some_condition THEN
--Here is possible to cancel all above operations?
RETURN FALSE;
END IF;
RETURN TRUE;
END;
$$
LANGUAGE plpgsql;
Both answers so far are incorrect.
If you try to start a transaction or use a SAVEPOINT inside a plpgsql function you get an error message like this:
ERROR: cannot begin/end transactions in PL/pgSQL
HINT: Use a BEGIN block with an EXCEPTION clause instead.
CONTEXT: PL/pgSQL function "f_savepoint" line 6 at SQL statement
If you try a SAVEPOINT inside a plain SQL function:
ERROR: SAVEPOINT is not allowed in a SQL function
CONTEXT: SQL function "f_savepoint2" during startup
As the error message instructs, use a BEGIN block inside a plpgsql function instead. Your demo could look like this:
CREATE OR REPLACE FUNCTION transact_test(boolean)
RETURNS boolean AS
$func$
BEGIN -- start a nested BEGIN block
UPDATE t SET i = i+1 WHERE i = 1;
UPDATE t SET i = i+1 WHERE i = 3;
IF $1 THEN
RAISE EXCEPTION 'foo'; -- cancels all of the above
END IF;
RETURN TRUE;
EXCEPTION WHEN OTHERS THEN
RETURN FALSE;
-- do nothing
END
$func$ LANGUAGE plpgsql;
-> SQLfiddle demonstrating it all.
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 $$