I am developing a function in Postgres which aims to recover for each record of a query the value of a result of a check contained in a set of functions. Only one of these functions will return the correct value. These functions have a comun prefix 'fn_condicao_' and receives as a parameter an object of type 'my_table'.
As the number of functions that make the check is unknown, I decided to consult the Postgres catalog, from the table pg_catalog.pg_proc
searching for functions with the prefix 'fn_condicao_' and dynamically execute them with EXECUTE.
My problem is how to pass the correct shape parameter for EXECUTE.
create or replace function test_conditions()
returns void as
$$
declare
v_record my_table%rowtype;
v_function pg_proc%rowtype;
begin
set search_path = 'pg_catalog';
for v_record in (select * from my_table where id in (1,2,3)) loop
for v_function in (
SELECT p.proname
FROM pg_namespace n
JOIN pg_proc p
ON p.pronamespace = n.oid
WHERE n.nspname = 'operacional'
and p.proname like ('fn_condition\\_%')
order by p.proname)
loop
--execute 'select ' || v_function.proname || '(' || v_record || ')';
end loop;
end loop;
end;
$$
language plpgsql;
How to pass v_record
properly in the commented EXECUTE
command in the function above?
execute 'select ' || v_function.proname || '(' || v_record || ')'; -- ???
Example function:
create or replace function fn_condition_1(p_record my_table)
returns bigint as
$$
begin
if ($1.atributo1 > $1.atributo2) then
return 1;
end if;
return null;
end;
$$
language plpgsql;
This question was answered in DataBase Administrators by Erwin Brandstetter. So, I would like to share with you the resolution.
DataBase Administrators answer
In Postgres 8.4 or later you would use the
USING
clause ofEXECUTE
to pass values safely and efficiently. That's not available in your version 8.3, yet. In your version it could could work like this:Call:
If you use
set search_path = 'pg_catalog';
in the function body, then your table in thepublic
schema is not visible any more. And it would be a very bad idea to globallySET
the search path. The effect stays for the duration of the setting. You could useSET LOCAL
to contain it to the transaction, but that would still be a bad idea. Instead, if you really need to, set the environment of the function only, like demonstrated.More about the search path in Postgres:
Just executing a
SELECT
without assigning or returning the result would be pointless. Use theINTO
clause ofEXECUTE
and thenRETURN NEXT
. In modern Postgres you would replace the inner loop withRETURN QUERY EXECUTE
.Use
quote_ident()
andquote_literal()
to escape identifiers and literals properly when building a dynamic query string. In modern Postgres you would useformat()
.It's not very efficient to cast the whole row to it's string representation, escape and cast back. This alternative approach has to read from the table repeatedly, but is cleaner otherwise (the row is passed as value directly):
Example function
You can also radically simplify your example function with this SQL function: