Continue a PL/pgSQL block even if it finds an error - postgresql

I've gotten the essence of the function below from: How to re-check an SQL function created with check_function_bodies=false?
The context is: I'm migrating some functions from Oracle to PostgreSQL. While migrating them, I used the option which establishes a non-verification of their bodies, so that all function could be created without being compiled/verified, what would "speed" the process. Therefore, now - using the function bellow as a mean - I am trying to make an analysis of each function created in this X schema.
My problem is that the function doesn't continue when it finds an error. My central thought is to run the recompile_function() once and get all the messages fired when an error is found for each function. I have tried to enclosure the statement that verifies the function within a sub-block (BEGIN, EXCEPTION, END). It didn't work though.
What am I missing here?
CREATE OR REPLACE FUNCTION public.recompile_functions()
RETURNS void
LANGUAGE plpgsql
AS
$function$
DECLARE
l_func regprocedure;
BEGIN
--test plpgsql functions
FOR l_func IN (
SELECT oid
FROM pg_proc
WHERE pronamespace='<<schema>>'::regnamespace
AND prolang=(SELECT oid FROM pg_language WHERE lanname='plpgsql')
AND pg_proc.oid NOT IN (select tg.tgfoid FROM pg_trigger tg)
AND pg_proc.prokind = 'f'
)
LOOP
BEGIN
PERFORM plpgsql_validator(l_func);
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Function % failed validation checks: %', l_func::text, SQLERRM;
END;
END LOOP;
END;
$function$

Related

PL/pgSQL procedures and transaction control

I'm new to Postgres, but with experience from Oracle. Trying to create a stored procedure which is going to:
Insert a row
Handle exceptions and in case of an exception insert a row into a log table by calling dedicated procedure
Emit an audit log record into a log table in case the whole procedure ran successfully
By pseudo code:
CREATE OR REPLACE PROCEDURE test.p_insert(IN p_test_param character varying)
LANGUAGE 'plpgsql'
SECURITY DEFINER
AS $BODY$
DECLARE
-- some declarations
BEGIN
BEGIN
INSERT INTO test.a(a) VALUES (p_test_param);
EXCEPTION
WHEN OTHERS THEN
-- GET STACKED DIAGNOSTICS
CALL test.p_insert_log(...); -- Inserts a row into a log table, another COMMIT may be required?
RAISE;
END;
COMMIT; -- CAN'T DO
BEGIN
IF (SELECT test.f_debug()) THEN
CALL test.p_insert_log(...); -- Audit the execution
END IF;
END;
COMMIT; -- CAN'T DO EITHER
END;
$$BODY$$;
However when I try to test the procedure out from an anonymous block in PgAdmin such as:
BEGIN;
DO
LANGUAGE plpgsql
$$
BEGIN
CALL test.p_insert(
p_test_param => 'test'
);
END;
$$
I'm getting an error ERROR: invalid transaction termination. How can I get rid of it? My objective is to let the procedure carry out the transaction control, I don't want the caller to COMMIT or ROLLBACK anything. If I remove both COMMIT commands from the code of the procedure, it executes well, however the invoker must explicitly COMMIT or REVOKE the transaction afterwards, which is not desired. In Oracle the pseudo code with COMMIT statements would work, in Postgres it doesn't seem to work as I would like to. Could you please help me out? Thanks
Your code will work as intended. Perhaps you made some mistake in calling the code:
you cannot call the procedure from a function
you cannot call the procedure in an explicitly started transaction:
BEGIN;
CALL p_insert('something); -- will fail
COMMIT;

call procedure from a function in postgresql

I have a procedure which has commit in it (postgresql v11), would like to call his procedure from a function. Getting below error as the procedure has commit, when commit is removed it works fine. Is there any work around to be able to call procedure with transaction control statements from a function (I know function does not support transactions and thats why its failing, but want to know if there is any alternative?)
CREATE OR REPLACE FUNCTION public.f()
RETURNS text
LANGUAGE plpgsql
AS $function$
BEGIN
raise notice 'Starting Function!!!';
call public.p();
return 'success' ;
END;
$function$;
CREATE OR REPLACE PROCEDURE public.p() LANGUAGE plpgsql AS $$
DECLARE src_schema TEXT;
BEGIN
raise notice 'Starting Procedure!!!';
commit;
RETURN;
END;
$$;
-- if the procedure has commit then its failing
imaods=> select public.f();
NOTICE: Starting Function!!!
NOTICE: Starting Procedure!!!
ERROR: invalid transaction termination
CONTEXT: PL/pgSQL function p() line 5 at COMMIT
SQL statement "CALL public.p()"
PL/pgSQL function f() line 4 at CALL
-- if the commit is removed from the procedure then it works fine
imaods=> CREATE OR REPLACE PROCEDURE public.p() LANGUAGE plpgsql AS $$
imaods$> DECLARE src_schema TEXT;
imaods$> BEGIN
imaods$> raise notice 'Starting Procedure!!!';
imaods$> RETURN;
imaods$> END;
imaods$> $$;
CREATE PROCEDURE
imaods=>
imaods=> select public.f();
NOTICE: Starting Function!!!
NOTICE: Starting Procedure!!!
f
---------
success
(1 row)
The documentation explains that:
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.
The reason is that you cannot have transaction management in PostgreSQL functions.

Postgresql how to multiple stored procedure in transaction

I have many stored procedure in my postgresql db,
and for some reason i need to run many procedure in transaction so if there is a error it will rollback.
is there any way to do this?
edit 1
i run this through java and for some reason i cant make transaction from java and i cant run query string, just store procedure only.
I actually thinking making procedure like this
CREATE OR REPLACE FUNCTION ldt_pricing_rule_v1_api.start()
RETURNS VOID
LANGUAGE PLPGSQL
SECURITY DEFINER
AS $$
BEGIN
EXECUTE 'begin transaction'
RETURN;
END
$$;
select ldt_pricing_rule_v1_api.start();
but it's will display this
ERROR: cannot begin/end transactions in PL/pgSQL
HINT: Use a BEGIN block with an EXCEPTION clause instead.
BEGIN ... COMMIT should to work.
BEGIN
SELECT func1();
SELECT func2();
COMMIT;
PostgreSQL 11 (it is not released yet) has procedures where you can control transactions explicitly. Procedures are started by CALL statement like any other databases. Now, PostgreSQL functions doesn't allow control transactions (explicitly).
Any PostgreSQL function is executed under transaction - explicitly started by user (like my example), or implicitly started by system (by autocommit mode).
So outer BEGIN starts explicit transaction:
BEGIN
SELECT func1();
SELECT func2();
COMMIT;
and if there is any unhandled fail, then only ROLLBACK command is available.
or implicit transaction:
CREATE OR REPLACE FUNCTION outerfx()
RETURNS void AS $$
BEGIN
PERFORM func1();
PERFORM func2();
END;
$$ LANGUAGE plpgsql;
SELECT outerfx(); -- starts outer transaction implicitly.
Now, functions func1, func2 are executed under transaction too.

How to run a PostgreSQL function

I am struggling to execute a PostgreSQL function. I am trying to read the documentation on the side but still no use. I am using Toad Extension for eclipse to develop/run the function
So far, this is what I have written
CREATE OR REPLACE FUNCTION dbName.function_name()
RETURNS VOID AS
$BODY$
DECLARE
x_cur CURSOR FOR select * from dbName.x;
x_row RECORD;
BEGIN
OPEN x_cur;
RAISE NOTICE 'Cursor opened';
LOOP
FETCH x_cur INTO x_row;
EXIT WHEN NOT FOUND;
END LOOP;
CLOSE x_cur;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
I executed the script and then ran select dbName.function_name(); in the sql worksheet and I don't see any output.
I remember writing functions on plsqldeveloper was so easy and interactive and i am struggling with PostgreSQL, could you guys help me getting a headstart.
I dont see any problem using pgAdmin.
The function return VOID, but the RAISE NOTICE show the message.
I simplify the function
CREATE OR REPLACE FUNCTION function_name()
RETURNS VOID AS
$BODY$
DECLARE
BEGIN
RAISE NOTICE 'Cursor opened';
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
And execute the function
SELECT function_name();

How to get a statement calling the function from inside the function itself?

Let's say I have a function show_files(IN file text, IN suffix text, OUT statement text). In next step the function is called:
SELECT * FROM show_files(file := 'example', suffix := '.png');
My question is: Is there any solution that I could get statement that has called this function from inside that function?
I mean, after running the SELECT the output of function (OUT statement text) should be: 'SELECT * FROM show_files(file := 'example', suffix := '.png');', or is it possible to assign this statement to the variable inside the function?
I need the functionality like those with TG_NAME, TG_OP, etc. in trigger procedures.
Maybe is it possible to retrieve this statement from SELECT current_query FROM pg_stat_activity ?
When I'm trying to use it inside a function I've got an empty record:
CREATE OR REPLACE FUNCTION f_snitch(text)
RETURNS text AS
$BODY$
declare
rr text;
BEGIN
RAISE NOTICE '.. from f_snitch.';
-- do stuff
SELECT current_query into rr FROM pg_stat_activity
WHERE current_query ilike 'f_snitch';
RETURN rr;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Any help and suggestions would be happily welcome!
TG_NAME and friends are special variables that only exist for trigger functions. Regular plpgsql functions don't have anything like that. I am fresh out of ideas how you could possibly get this inside the called function in plpgsql.
You could add RAISE NOTICE to your function so you get the desired information
CREATE OR REPLACE FUNCTION f_snitch(text)
RETURNS text LANGUAGE plpgsql AS
$func$
BEGIN
RAISE NOTICE '.. from f_snitch.';
-- do stuff
RETURN 'Snitch says hi!';
END
$func$;
Call:
SELECT f_snitch('foo')
In addition to the result, this returns a notice:
NOTICE: .. from f_snitch.
Fails to please in two respects:
Calling statement is not in the notice.
No CONTEXT in the notice.
For 1. you can use RAISE LOG instead (or set your cluster up to log NOTICES, too - which I usually don't, too verbose for me). With standard settings, you get an additional line with the STATEMENT in the database log:
LOG: .. from f_snitch.
STATEMENT: SELECT f_snitch('foo')
For 2., have a look at this related question at dba.SE. CONTEXT would look like:
CONTEXT: SQL statement "SELECT f_raise('LOG', 'My message')"
PL/pgSQL function "f_snitch" line 5 at PERFORM
Ok, I've got it!
CREATE OR REPLACE FUNCTION f_snitch(text)
RETURNS setof record AS
$BODY$
BEGIN
RETURN QUERY
SELECT current_query
FROM pg_stat_activity
<strike>ORDER BY length(current_query) DESC LIMIT 1;</strike>
where current_query ilike 'select * from f_snitch%';
-- much more reliable solution
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
select * from f_snitch('koper') AS (tt text);
And here is the result:
It's probably not 100% reliable solution but for small systems (for few users) it's quite ok.