plpgsql_check插件,用于解决plpgsql相关的问题,比如,对象是否定义,检测类型的一致性,类型定义错误,SQL注入,性能分析,未使用变量或者参数等等
PostgreSQL 9.5以上均支持改插件
git地址:
https://github.com/okbob/plpgsql_check#tracer
安装:
unzip /home/postgres/plpgsql_check-master.zip
chown -R postgres.postgres plpgsql_check-master
cd plpgsql_check-master
make USE_PGXS=1 clean
make USE_PGXS=1 install
以下例子说明了,f1函数调用了t1的c列,但是该表没有c列,主动使用plpgsql_check_function_tb可以检测出该错误
postgres=# CREATE EXTENSION plpgsql_check;
LOAD
postgres=# CREATE TABLE t1(a int, b int);
CREATE TABLE
postgres=#
CREATE OR REPLACE FUNCTION public.f1()
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE r record;
BEGIN
FOR r IN SELECT * FROM t1
LOOP
RAISE NOTICE '%', r.c; -- there is bug - table t1 missing "c" column
END LOOP;
END;
$function$;
CREATE FUNCTION
postgres=# select f1(); -- execution doesn't find a bug due to empty table t1
f1
────
(1 row)
postgres=# \x
Expanded display is on.
postgres=# select * from plpgsql_check_function_tb('f1()');
─[ RECORD 1 ]───────────────────────────
functionid │ f1
lineno │ 6
statement │ RAISE
sqlstate │ 42703
message │ record "r" has no field "c"
detail │ [null]
hint │ [null]
level │ error
position │ 0
query │ [null]
postgres=# \sf+ f1
CREATE OR REPLACE FUNCTION public.f1()
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE r record;
BEGIN
FOR r IN SELECT * FROM t1
LOOP
RAISE NOTICE '%', r.c; -- there is bug - table t1 missing "c" column
END LOOP;
END;
$function$
可以使用以下语句大批量检测相关函数
-- check all nontrigger plpgsql functions
SELECT p.oid, p.proname, plpgsql_check_function(p.oid)
FROM pg_catalog.pg_namespace n
JOIN pg_catalog.pg_proc p ON pronamespace = n.oid
JOIN pg_catalog.pg_language l ON p.prolang = l.oid
WHERE l.lanname = 'plpgsql' AND p.prorettype <> 2279;
或者
SELECT p.proname, tgrelid::regclass, cf.*
FROM pg_proc p
JOIN pg_trigger t ON t.tgfoid = p.oid
JOIN pg_language l ON p.prolang = l.oid
JOIN pg_namespace n ON p.pronamespace = n.oid,
LATERAL plpgsql_check_function(p.oid, t.tgrelid) cf
WHERE n.nspname = 'public' and l.lanname = 'plpgsql'
或者
-- check all plpgsql functions (functions or trigger functions with defined triggers)
SELECT
(pcf).functionid::regprocedure, (pcf).lineno, (pcf).statement,
(pcf).sqlstate, (pcf).message, (pcf).detail, (pcf).hint, (pcf).level,
(pcf)."position", (pcf).query, (pcf).context
FROM
(
SELECT
plpgsql_check_function_tb(pg_proc.oid, COALESCE(pg_trigger.tgrelid, 0)) AS pcf
FROM pg_proc
LEFT JOIN pg_trigger
ON (pg_trigger.tgfoid = pg_proc.oid)
WHERE
prolang = (SELECT lang.oid FROM pg_language lang WHERE lang.lanname = 'plpgsql') AND
pronamespace <> (SELECT nsp.oid FROM pg_namespace nsp WHERE nsp.nspname = 'pg_catalog') AND
-- ignore unused triggers
(pg_proc.prorettype <> (SELECT typ.oid FROM pg_type typ WHERE typ.typname = 'trigger') OR
pg_trigger.tgfoid IS NOT NULL)
OFFSET 0
) ss
ORDER BY (pcf).functionid::regprocedure::text, (pcf).lineno
在启动时就有检查功能-必须加载plpgsql_check模块。
可以在postgresql.conf中加入该参数
plpgsql_check.mode = [ disabled | by_function | fresh_start | every_start ]
plpgsql_check.fatal_errors = [ yes | no ]
plpgsql_check.show_nonperformance_warnings = false
plpgsql_check.show_performance_warnings = false
还需要shared_preload_libraries加入我们用的插件,如下:
hank=# show shared_preload_libraries ;
shared_preload_libraries
----------------------------------
pg_pathman,plpgsql,plpgsql_check
如:
#plpgsql_check
plpgsql_check.mode = every_start
plpgsql_check.fatal_errors = on
plpgsql_check.show_nonperformance_warnings = true
plpgsql_check.show_performance_warnings = true
通过以下方式启动被动模式:
load 'plpgsql'; -- 1.1 and higher doesn't need it
load 'plpgsql_check';
set plpgsql_check.mode = 'every_start';
SELECT fx(10); -- run functions - function is checked before runtime starts it
plpgsql_check无法验证对在plpgsql函数运行时中创建的临时表的查询。以下例子,有必要创建一个伪造的临时表或为此功能禁用plpgsql_check。实际中,临时表以比持久表有更高的优先级存储在自己的(每用户)模式中。因此,您可以这样做
CREATE OR REPLACE FUNCTION public.disable_dml()
RETURNS trigger
LANGUAGE plpgsql AS $function$
BEGIN
RAISE EXCEPTION SQLSTATE '42P01'
USING message = format('this instance of %I table doesn''t allow any DML operation', TG_TABLE_NAME),
hint = format('you should to run "CREATE TEMP TABLE %1$I(LIKE %1$I INCLUDING ALL);" statement',
TG_TABLE_NAME);
RETURN NULL;
END;
$function$;
CREATE TABLE foo(a int, b int); -- doesn't hold data ever
CREATE TRIGGER foo_disable_dml
BEFORE INSERT OR UPDATE OR DELETE ON foo
EXECUTE PROCEDURE disable_dml();
postgres=# INSERT INTO foo VALUES(10,20);
ERROR: this instance of foo table doesn't allow any DML operation
HINT: you should to run "CREATE TEMP TABLE foo(LIKE foo INCLUDING ALL);" statement
postgres=#
CREATE TABLE
postgres=# INSERT INTO foo VALUES(10,20);
INSERT 0 1
hank=# CREATE OR REPLACE FUNCTION public.fx1(a integer)
hank-# RETURNS integer
hank-# LANGUAGE plpgsql
hank-# AS $function$
hank$# begin
hank$# if a > 10 then
hank$# raise notice 'ahoj';
hank$# return -1;
hank$# else
hank$# raise notice 'nazdar';
hank$# return 1;
hank$# end if;
hank$# end;
hank$# $function$;
CREATE FUNCTION
hank=#
hank=#
每个语句(不是每行)的概要可以通过plpgsql_profiler_function_statements_tb查看
hank=# select stmtid, parent_stmtid, parent_note, lineno, exec_stmts, stmtname
hank-# from plpgsql_profiler_function_statements_tb('fx1');
stmtid | parent_stmtid | parent_note | lineno | exec_stmts | stmtname
--------+---------------+-------------+--------+------------+-----------------
0 | | | 2 | 0 | statement block
1 | 0 | body | 3 | 0 | IF
2 | 1 | then body | 4 | 0 | RAISE
3 | 1 | then body | 5 | 0 | RETURN
4 | 1 | else body | 7 | 0 | RAISE
5 | 1 | else body | 8 | 0 | RETURN
(6 rows)
可以通过plpgsql_profiler_function_tb查看fx1的分析结果
hank=# select lineno, avg_time, source from plpgsql_profiler_function_tb('fx1(int)');
lineno | avg_time | source
--------+----------+-----------------------------
1 | |
2 | | begin
3 | | if a > 10 then
4 | | raise notice 'ahoj';
5 | | return -1;
6 | | else
7 | | raise notice 'nazdar';
8 | | return 1;
9 | | end if;
10 | | end;
11 | |
(11 rows)
其他有关trace,sql安装,以及安全相关的示例这里不再演示,请参考以下链接:
https://github.com/digoal/blog/blob/master/201908/20190831_01.md
https://github.com/okbob/plpgsql_check#tracer