Skip to content

Commit 4e53098

Browse files
committed
finalize 1.7 release
1 parent 5963e27 commit 4e53098

12 files changed

+1241
-177
lines changed

META.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
"name": "plpgsql_check",
33
"abstract": "Additional tools for plpgsql functions validation",
44
"description": "The plpgsql_check is PostgreSQL extension with functionality for direct or indirect extra validation of functions in plpgsql language. It verifies a validity of SQL identifiers used in plpgsql code. It try to identify a performance issues. Modern versions has integrated profiler. The table and function dependencies can be displayed",
5-
"version": "1.5.0",
5+
"version": "1.7.0",
66
"maintainer": "Pavel STEHULE <pavel.stehule@gmail.com>",
77
"license": "bsd",
88
"provides": {
99
"plpgsql_check": {
1010
"abstract": "Additional tools for plpgsql functions validation",
1111
"file": "sql/plpgsql_check_active.sql",
1212
"docfile": "README.md",
13-
"version": "1.5.0"
13+
"version": "1.7.0"
1414
}
1515
},
1616
"prereqs": {

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ google group.
2121
* detection of missing RETURN command in function
2222
* try to identify unwanted hidden casts, that can be performance issue like unused indexes
2323
* possibility to collect relations and functions used by function
24+
* possibility to check EXECUTE stmt agaist SQL injection vulnerability
2425

2526
I invite any ideas, patches, bugreports
2627

@@ -151,6 +152,9 @@ You can set level of warnings via function's parameters:
151152
declared type with type modificator, casting, implicit casts in where clause (can be
152153
reason why index is not used), ..
153154

155+
* `security_warnings boolean DEFAULT false` - security related checks like SQL injection
156+
vulnerability detection
157+
154158
## Triggers
155159

156160
When you want to check any trigger, you have to enter a relation that will be
@@ -183,6 +187,26 @@ Correct trigger checking (with specified relation)
183187
error:42703:3:assignment:record "new" has no field "c"
184188
(1 row)
185189

190+
For triggers with transitive tables you can set a `oldtable` or `newtable` parameters:
191+
192+
create or replace function footab_trig_func()
193+
returns trigger as $$
194+
declare x int;
195+
begin
196+
if false then
197+
-- should be ok;
198+
select count(*) from newtab into x;
199+
200+
-- should fail;
201+
select count(*) from newtab where d = 10 into x;
202+
end if;
203+
return null;
204+
end;
205+
$$ language plpgsql;
206+
207+
select * from plpgsql_check_function('footab_trig_func','footab', newtable := 'newtab');
208+
209+
186210
## Mass check
187211

188212
You can use the plpgsql_check_function for mass check functions and mass check

expected/plpgsql_check_active_1.out

Lines changed: 254 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2965,13 +2965,22 @@ select testseq();
29652965
ERROR: "test_table" is not a sequence
29662966
CONTEXT: SQL statement "SELECT nextval('test_table')"
29672967
PL/pgSQL function testseq() line 3 at PERFORM
2968-
select * from plpgsql_check_function('testseq()');
2968+
select * from plpgsql_check_function('testseq()', fatal_errors := false);
29692969
plpgsql_check_function
29702970
------------------------------------------------------
29712971
error:42809:3:PERFORM:"test_table" is not a sequence
29722972
Query: SELECT nextval('test_table')
29732973
-- ^
2974-
(3 rows)
2974+
error:42809:4:PERFORM:"test_table" is not a sequence
2975+
Query: SELECT currval('test_table')
2976+
-- ^
2977+
error:42809:5:PERFORM:"test_table" is not a sequence
2978+
Query: SELECT setval('test_table', 10)
2979+
-- ^
2980+
error:42809:6:PERFORM:"test_table" is not a sequence
2981+
Query: SELECT setval('test_table', 10, true)
2982+
-- ^
2983+
(12 rows)
29752984

29762985
drop function testseq();
29772986
drop table test_table;
@@ -3550,6 +3559,61 @@ select * from plpgsql_check_function('test_crash', fatal_errors := true);
35503559
(5 rows)
35513560

35523561
drop function test_crash();
3562+
-- fix false alarm reported by Piotr Stepniewski
3563+
create or replace function public.fx()
3564+
returns void
3565+
language plpgsql
3566+
as $function$
3567+
begin
3568+
raise exception 'xxx';
3569+
end;
3570+
$function$;
3571+
-- show raise nothing
3572+
select * from plpgsql_check_function('fx()');
3573+
plpgsql_check_function
3574+
------------------------
3575+
(0 rows)
3576+
3577+
create table errtab(
3578+
message text,
3579+
code character(5)
3580+
);
3581+
create or replace function public.fx()
3582+
returns void
3583+
language plpgsql
3584+
as $function$
3585+
declare
3586+
var errtab%rowtype;
3587+
begin
3588+
raise exception using message = var.message, errcode = var.code;
3589+
end;
3590+
$function$;
3591+
-- should not to crash
3592+
select * from plpgsql_check_function('fx()');
3593+
plpgsql_check_function
3594+
------------------------
3595+
(0 rows)
3596+
3597+
create or replace function public.fx()
3598+
returns void
3599+
language plpgsql
3600+
as $function$
3601+
declare
3602+
var errtab%rowtype;
3603+
begin
3604+
raise exception using message = var.message, errcode = var.code, hint = var.hint;
3605+
end;
3606+
$function$;
3607+
-- should not to crash
3608+
select * from plpgsql_check_function('fx()');
3609+
plpgsql_check_function
3610+
------------------------------------------------------
3611+
error:42703:5:RAISE:record "var" has no field "hint"
3612+
Query: SELECT var.hint
3613+
-- ^
3614+
(3 rows)
3615+
3616+
drop function fx();
35533617
create or replace function foo_format(a text, b text)
35543618
returns void as $$
35553619
declare s text;
@@ -3610,16 +3674,17 @@ select * from plpgsql_check_function('dyn_sql_1', security_warnings := true, fat
36103674
Detail: The EXECUTE expression is SQL injection vulnerable.
36113675
Hint: Use quote_ident, quote_literal or format function to secure variable.
36123676
warning:00000:13:EXECUTE:values passed to EXECUTE statement by USING clause was not used
3613-
error:XX000:14:EXECUTE:there is no parameter $1
3614-
Context: SQL statement "select $1"
3615-
(13 rows)
3677+
error:42P02:14:EXECUTE:there is no parameter $1
3678+
Query: select $1
3679+
-- ^
3680+
(14 rows)
36163681

36173682
drop function dyn_sql_1();
36183683
create type tp as (a int, b int);
36193684
create or replace function dyn_sql_2()
36203685
returns void as $$
36213686
declare
3622-
r tp;
3687+
r tp;
36233688
result int;
36243689
begin
36253690
select 10 a, 20 b into r;
@@ -3634,7 +3699,189 @@ select * from plpgsql_check_function('dyn_sql_2', security_warnings := true);
36343699
------------------------------------------------------------
36353700
error:42703:9:EXECUTE:column "c" not found in data type tp
36363701
Query: select $1.c
3637-
-- ^
3702+
-- ^
3703+
(3 rows)
3704+
3705+
drop function dyn_sql_2();
3706+
drop type tp;
3707+
/*
3708+
* Should not to work
3709+
*
3710+
* note: plpgsql doesn't support passing some necessary details for record
3711+
* type. The parser setup for dynamic SQL column doesn't use ref hooks, and
3712+
* then it cannot to pass TupleDesc info to query anyway.
3713+
*/
3714+
create or replace function dyn_sql_2()
3715+
returns void as $$
3716+
declare
3717+
r record;
3718+
result int;
3719+
begin
3720+
select 10 a, 20 b into r;
3721+
raise notice '%', r.a;
3722+
execute 'select $1.a + $1.b' into result using r;
3723+
raise notice '%', result;
3724+
end;
3725+
$$ language plpgsql;
3726+
select dyn_sql_2(); --should to fail
3727+
NOTICE: 10
3728+
ERROR: could not identify column "a" in record data type
3729+
LINE 1: select $1.a + $1.b
3730+
^
3731+
QUERY: select $1.a + $1.b
3732+
CONTEXT: PL/pgSQL function dyn_sql_2() line 8 at EXECUTE
3733+
select * from plpgsql_check_function('dyn_sql_2', security_warnings := true);
3734+
plpgsql_check_function
3735+
-------------------------------------------------------------------------
3736+
error:42703:8:EXECUTE:could not identify column "a" in record data type
3737+
Query: select $1.a + $1.b
3738+
-- ^
36383739
(3 rows)
36393740

36403741
drop function dyn_sql_2();
3742+
create or replace function dyn_sql_3()
3743+
returns void as $$
3744+
declare r int;
3745+
begin
3746+
execute 'select $1' into r using 1;
3747+
raise notice '%', r;
3748+
end
3749+
$$ language plpgsql;
3750+
select dyn_sql_3();
3751+
NOTICE: 1
3752+
dyn_sql_3
3753+
-----------
3754+
3755+
(1 row)
3756+
3757+
-- should be ok
3758+
select * from plpgsql_check_function('dyn_sql_3');
3759+
plpgsql_check_function
3760+
------------------------
3761+
(0 rows)
3762+
3763+
create or replace function dyn_sql_3()
3764+
returns void as $$
3765+
declare r record;
3766+
begin
3767+
execute 'select $1 as a, $2 as b' into r using 1, 2;
3768+
raise notice '% %', r.a, r.b;
3769+
end
3770+
$$ language plpgsql;
3771+
select dyn_sql_3();
3772+
NOTICE: 1 2
3773+
dyn_sql_3
3774+
-----------
3775+
3776+
(1 row)
3777+
3778+
-- should be ok
3779+
select * from plpgsql_check_function('dyn_sql_3');
3780+
plpgsql_check_function
3781+
------------------------
3782+
(0 rows)
3783+
3784+
create or replace function dyn_sql_3()
3785+
returns void as $$
3786+
declare r record;
3787+
begin
3788+
execute 'create table foo(a int)' into r using 1, 2;
3789+
raise notice '% %', r.a, r.b;
3790+
end
3791+
$$ language plpgsql;
3792+
-- raise a error
3793+
select * from plpgsql_check_function('dyn_sql_3');
3794+
plpgsql_check_function
3795+
-----------------------------------------------------------------------------------------
3796+
warning:00000:4:EXECUTE:values passed to EXECUTE statement by USING clause was not used
3797+
error:XX000:4:EXECUTE:expression does not return data
3798+
(2 rows)
3799+
3800+
create or replace function dyn_sql_3()
3801+
returns void as $$
3802+
declare r1 int; r2 int;
3803+
begin
3804+
execute 'select 1' into r1, r2 using 1, 2;
3805+
raise notice '% %', r1, r2;
3806+
end
3807+
$$ language plpgsql;
3808+
-- raise a error
3809+
select * from plpgsql_check_function('dyn_sql_3');
3810+
plpgsql_check_function
3811+
-----------------------------------------------------------------------------------------
3812+
warning:00000:4:EXECUTE:values passed to EXECUTE statement by USING clause was not used
3813+
warning:00000:4:EXECUTE:too few attributes for target variables
3814+
Detail: There are more target variables than output columns in query.
3815+
Hint: Check target variables in SELECT INTO statement.
3816+
(4 rows)
3817+
3818+
drop function dyn_sql_3();
3819+
create or replace function dyn_sql_3()
3820+
returns void as $$
3821+
declare r record;
3822+
begin
3823+
for r in execute 'select 1 as a, 2 as b'
3824+
loop
3825+
raise notice '%', r.a;
3826+
end loop;
3827+
end
3828+
$$ language plpgsql;
3829+
-- should be ok
3830+
select * from plpgsql_check_function('dyn_sql_3');
3831+
plpgsql_check_function
3832+
------------------------
3833+
(0 rows)
3834+
3835+
drop function dyn_sql_3();
3836+
create or replace function dyn_sql_3()
3837+
returns void as $$
3838+
declare r record;
3839+
begin
3840+
for r in execute 'select 1 as a, 2 as b'
3841+
loop
3842+
raise notice '%', r.c;
3843+
end loop;
3844+
end
3845+
$$ language plpgsql;
3846+
-- should be error
3847+
select * from plpgsql_check_function('dyn_sql_3');
3848+
plpgsql_check_function
3849+
-------------------------------------------------
3850+
error:42703:6:RAISE:record "r" has no field "c"
3851+
Context: SQL statement "SELECT r.c"
3852+
(2 rows)
3853+
3854+
drop function dyn_sql_3();
3855+
create or replace function dyn_sql_4()
3856+
returns table(ax int, bx int) as $$
3857+
begin
3858+
return query execute 'select 10, 20';
3859+
return;
3860+
end;
3861+
$$ language plpgsql;
3862+
-- should be ok
3863+
select * from plpgsql_check_function('dyn_sql_4()');
3864+
plpgsql_check_function
3865+
------------------------
3866+
(0 rows)
3867+
3868+
create or replace function dyn_sql_4()
3869+
returns table(ax int, bx int) as $$
3870+
begin
3871+
return query execute 'select 10, 20, 30';
3872+
return;
3873+
end;
3874+
$$ language plpgsql;
3875+
select * from dyn_sql_4();
3876+
ERROR: structure of query does not match function result type
3877+
DETAIL: Number of returned columns (3) does not match expected column count (2).
3878+
CONTEXT: PL/pgSQL function dyn_sql_4() line 3 at RETURN QUERY
3879+
-- should be error
3880+
select * from plpgsql_check_function('dyn_sql_4()');
3881+
plpgsql_check_function
3882+
-----------------------------------------------------------------------------------
3883+
error:42804:3:RETURN QUERY:structure of query does not match function result type
3884+
Detail: Number of returned columns (3) does not match expected column count (2).
3885+
(2 rows)
3886+
3887+
drop function dyn_sql_4();

0 commit comments

Comments
 (0)