如何记录由 pl/pgsql 函数执行的 DML 语句?

Séb*_*ent 7 postgresql dml plpgsql functions log

我有一个 pl/pgsql 函数(见下文),它列出了一些字段并使用动态构造的 UPDATE 命令清除它们的内容。

当我设置时log_statement = 'mod',我在执行函数时在日志上看不到任何内容SELECT fnct_clear_temp_fields();。当我设置log_statement = 'all'并执行我可以SELECT fnct_clear_temp_fields();在日志中看到的函数时,但看不到底层的 UPDATE 命令。

有没有办法让 UPDATE 命令也出现在日志中?

有关信息,这里是功能:

CREATE OR REPLACE FUNCTION fnct_clear_temp_fields() RETURNS VOID AS $$
DECLARE
    --Put into a cursor a view dynamically listing all user-defined fields beginning with 'temp_'
    dataset_1 CURSOR FOR 
        SELECT 
            column_name,
            table_name
        FROM information_schema.tables 
        NATURAL JOIN information_schema.columns 
        WHERE 
            table_schema='public'
            AND table_type='BASE TABLE'
            AND column_name ~ '^temp_'
        ORDER BY table_name,column_name;

    --Record variable to go through each line of the view above
    dataset_1_row RECORD;

BEGIN
    OPEN dataset_1; --Open the cursor
    FETCH dataset_1 INTO dataset_1_row; --first row of the view

    WHILE FOUND LOOP
        RAISE NOTICE 'Table: %, Column: %',  dataset_1_row.table_name,dataset_1_row.column_name;

        --Set to NULL the contents of the current 'temp_' column
        EXECUTE 'UPDATE '||dataset_1_row.table_name||' SET '||dataset_1_row.column_name||'=NULL WHERE '||dataset_1_row.column_name||' IS NOT NULL';

        FETCH dataset_1 INTO dataset_1_row; --next row please.
    END LOOP; --while end

    CLOSE dataset_1;

    RETURN;
END;
$$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 8

提问

这里一个内置的方式来记录里面PLPGSQL功能的所有语句: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
Run Code Online (Sandbox Code Playgroud)

在这个密切相关的问题下的详细信息:
用 pgpsql 编写的 UDF 调用的 Postgres 查询计划

可能会生成大量日志输出。我只会将它用于调试,而不是用于生产。
如果您只需要记录一个语句,请使用@dezso 的建议

代码审计

考虑这个重写的函数:

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;
Run Code Online (Sandbox Code Playgroud)

要点

  • 必须清理您构建到动态 SQL 中的所有标识符,否则它可能会因需要双引号的非标准名称而失败。更糟糕的是,您对 SQL 注入持开放态度。
    演示quote_ident(),因为您多次使用经过消毒的标识符。有更多的选择与regclassformat()
    表名作为PostgreSQL的函数参数

  • 我更喜欢将此类查询基于系统目录而不是信息模式的缓慢视图。不过,这是一个要求和品味的问题。演示等效项,它的速度提高了约 10 倍(与UPDATE命令无关)。更多:
    如何检查给定模式中是否存在表

  • LIKE通常比更强大的正则表达式匹配 ( ~)更快。如果LIKE可以完成这项工作,请使用它。

  • 其他一些小的简化。

  • 有关更多详细信息的相关答案:
    更新表名是参数的游标记录


dez*_*zso 3

所以,我的建议作为实际答案:

RAISE LOG '%', your_statement;如果您仅在此函数中需要它,则可以在实际代码中执行, 或 :

...
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;
...
Run Code Online (Sandbox Code Playgroud)

另外,我发现

FOR dataset_1_row IN SELECT ... 
LOOP 
END LOOP;
Run Code Online (Sandbox Code Playgroud)

施工更加顺利。