How to log DML statements executed by a pl/pgsql function?
Question asked
There is a built-in way to log all statements inside plpgsql functions: auto-explain
LOAD 'auto_explain';
SET auto_explain.log_min_duration = 1; -- exclude very fast trivial queries
SET auto_explain.log_nested_statements = ON; -- statements inside functions
Details under this closely related question:
Postgres query plan of a UDF invocation written in pgpsql
Potentially generates a lot of log output. I would only use it for debugging, not in production.
If you just need the one statement logged, go with @dezso's advice.
Code audit
Consider this rewritten function:
CREATE OR REPLACE FUNCTION fnct_clear_temp_fields()
RETURNS void AS
$func$
DECLARE
rec record;
qry text;
BEGIN
FOR rec IN
SELECT quote_ident(c.relname) AS tbl, quote_ident(a.attname) AS col
FROM pg_namespace n
JOIN pg_class c ON c.relnamespace = n.oid
JOIN pg_attribute a ON a.attrelid = c.oid
WHERE n.nspname = 'public'
AND c.relkind = 'r'
AND a.attname LIKE 'temp_%' -- LIKE is faster than ~
AND a.attnum > 0
AND NOT a.attisdropped
ORDER BY 1,2
LOOP
RAISE NOTICE 'Table: %, Column: %', rec.tbl, rec.col;
qry := format('UPDATE %1$s SET %2$s = NULL WHERE %2$s IS NOT NULL', rec.tbl, rec.col);
RAISE LOG 'Query: %', qry;
EXECUTE qry;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Major points
You must sanitize all identifiers that you build into dynamic SQL, else it can fail with non-standard names that require double-quoting. Worse, you are open to SQL injection.
Demonstratingquote_ident()
, since you are using the sanitized identifiers multiple times. There are more options withregclass
orformat()
:
Table name as a PostgreSQL function parameterI prefer to base such queries on the system catalog instead of the painfully slow views of the information schema. That's a matter of requirements and taste, though. Demonstrating the equivalent, which is ~ 10 times faster (not regarding
UPDATE
commands). More:
How to check if a table exists in a given schemaLIKE
is generally faster than the more powerful regular expression matching (~
). IfLIKE
can do the job, use it.Some other minor simplifications.
Related answer with more details:
Update record of a cursor where the table name is a parameter
So, my suggestion as an actual answer:
If you need it only in this function, you can do a RAISE LOG '%', your_statement;
, or in your actual code:
...
DECLARE
exec_str text;
...
--Set to NULL the contents of the current 'temp_' column
exec_str := 'UPDATE '||dataset_1_row.table_name||
'SET '||dataset_1_row.column_name||'=NULL
WHERE '||dataset_1_row.column_name||' IS NOT NULL';
RAISE LOG 'Query executed: %', exec_str;
EXECUTE exec_str;
...
Also, I find the
FOR dataset_1_row IN SELECT ...
LOOP
END LOOP;
construct much smoother.