load 'plpgsql'; set client_min_messages to warning; create extension if not exists plpgsql_check; set client_min_messages to notice; set plpgsql_check.regress_test_mode = true; -- -- tests of plpgsql_make_pragma and "pragmas" option -- of check functions -- create table gtp_src(a int, b text); create type gtp_ct as (x int, y text); create domain gtp_dom as int check (value > 0); -- happy path - CREATE TEMP TABLE AS SELECT create function gtp_f1() returns void as $$ begin create temp table gtp_t1 as select a, b from gtp_src; insert into gtp_t1 values (10, 'hello'); end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f1()'); -- without pragmas the check should detect missing relation select * from plpgsql_check_function('gtp_f1()'); -- with generated pragmas the check should be ok select * from plpgsql_check_function('gtp_f1()', pragmas => array(select plpgsql_make_pragma('gtp_f1()'))); -- tabular form select * from plpgsql_check_function_tb('gtp_f1()', pragmas => array(select plpgsql_make_pragma('gtp_f1()'))); -- generator is idempotent - repeated call returns same result, -- and doesn't leave any relation in system catalogue select * from plpgsql_make_pragma('gtp_f1()'); select count(*) from pg_class where relname in ('gtp_t1'); -- happy path - one and two temporary tables created by AS VALUES and AS TABLE create function gtp_f2() returns void as $$ begin create temp table gtp_v1 as values (1, 'x'), (2, 'y'); end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f2()'); create function gtp_f3() returns void as $$ begin create temp table gtp_tt1 as table gtp_src; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f3()'); create function gtp_f4() returns void as $$ begin create temp table gtp_v1 as values (1, 'x'), (2, 'y'); create temp table gtp_tt1 as table gtp_src; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f4()'); drop function gtp_f2(); drop function gtp_f3(); drop function gtp_f4(); -- WITH NO DATA, IF NOT EXISTS and ON COMMIT variants create function gtp_f5() returns void as $$ begin create temp table gtp_nd as select a from gtp_src with no data; create temp table if not exists gtp_ine as select b from gtp_src; create temp table gtp_oc1 on commit preserve rows as select a from gtp_src; create temp table gtp_oc2 on commit delete rows as select a from gtp_src; create temp table gtp_oc3 on commit drop as select a from gtp_src; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f5()'); drop function gtp_f5(); -- explicitly entered column names create function gtp_f6() returns void as $$ begin create temp table gtp_cn(c1, c2) as select a, b from gtp_src; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f6()'); drop function gtp_f6(); -- star, join, subquery, CTE and recursive CTE create function gtp_f7() returns void as $$ begin create temp table gtp_star as select * from gtp_src; create temp table gtp_join as select s1.a, s2.b from gtp_src s1 join gtp_src s2 on s1.a = s2.a; create temp table gtp_sub as select a from gtp_src where a in (select a from gtp_src); create temp table gtp_cte as with x as (select a, b from gtp_src) select * from x; create temp table gtp_rcte as with recursive r(n) as (values (1) union all select n + 1 from r where n < 10) select n from r; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f7()'); drop function gtp_f7(); -- computed columns without aliases have name "?column?". Generated -- pragma returns these names without any change (the pragma is not -- applicable, and self registration fails with warning like CREATE -- TABLE AS would fail). create function gtp_f8() returns void as $$ begin create temp table gtp_comp as select a + 1, a + 1 from gtp_src; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f8()'); drop function gtp_f8(); -- unknown type is replaced by text like CREATE TABLE AS does create function gtp_f9() returns void as $$ begin create temp table gtp_unk as select null as x; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f9()'); drop function gtp_f9(); -- array, composite, domain types and typmods create function gtp_f10() returns void as $$ begin create temp table gtp_types as select array[1,2] as arr, (1, 'x')::gtp_ct as comp, 10::gtp_dom as dom, 'abc'::varchar(10) as vc, 3.14::numeric(12,2) as num; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f10()'); drop function gtp_f10(); -- identifiers that require quoting create function gtp_f11() returns void as $$ begin create temp table "MyT" as select 1 as "select", 2 as normal; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f11()'); drop function gtp_f11(); -- resjunk columns (ORDER BY) should not leak to generated pragma create function gtp_f12() returns void as $$ begin create temp table gtp_junk as select a from gtp_src order by b; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f12()'); drop function gtp_f12(); -- traversing of nested statements - IF/ELSE branches, loops, nested -- blocks, exception handlers and unreachable code create function gtp_f13() returns void as $$ declare i int; begin if random() > 0.5 then create temp table gtp_n1 as select 1 as x; else create temp table gtp_n2 as select 2 as y; end if; for i in 1..2 loop create temp table gtp_n3 as select 3 as z; end loop; declare v int; begin create temp table gtp_n4 as select 4 as w; exception when others then create temp table gtp_n5 as select 5 as e; end; return; create temp table gtp_n6 as select 6 as u; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f13()'); drop function gtp_f13(); -- positional processing - same table name twice (no deduplication), -- the following statements see the last definition create function gtp_f14() returns void as $$ begin create temp table gtp_dup as select 1 as x; drop table gtp_dup; create temp table gtp_dup as select 'a'::text as y; create temp table gtp_dup2 as select * from gtp_dup; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f14()'); drop function gtp_f14(); -- one temporary table references another one create function gtp_f15() returns void as $$ begin create temp table gtp_c1 as select a, b from gtp_src; create temp table gtp_c2 as select * from gtp_c1 where a > 0; create temp table gtp_c3 as select gtp_c1.a, gtp_c2.b from gtp_c1 join gtp_c2 on gtp_c1.a = gtp_c2.a; create temp table gtp_c4 as select a from gtp_src where a in (select a from gtp_c1); end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f15()'); drop function gtp_f15(); -- temporary table shadows an existing table with same name create table gtp_shadow(t text); create function gtp_f16() returns void as $$ begin create temp table gtp_shadow as select 1 as id; create temp table gtp_from_shadow as select * from gtp_shadow; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f16()'); drop function gtp_f16(); drop table gtp_shadow; -- explicitly used pg_temp schema create function gtp_f17() returns void as $$ begin create temp table pg_temp.gtp_q1 as select 1 as z; create table pg_temp.gtp_q2 as select 2 as w; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f17()'); drop function gtp_f17(); -- these forms are ignored - dynamic SQL, not temporary tables and -- materialized views create function gtp_f18() returns void as $$ begin execute 'create temp table gtp_s4 as select 1 as x'; create table gtp_s5 as select a from gtp_src; create unlogged table gtp_s6 as select a from gtp_src; create table gtp_s8 (id int); create unlogged table gtp_s9 (id int); create materialized view gtp_s7 as select a from gtp_src; drop table gtp_s4, gtp_s5, gtp_s6, gtp_s8, gtp_s9; drop materialized view gtp_s7; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f18()'); drop function gtp_f18(); -- function without temporary tables create function gtp_f19() returns int as $$ begin return (select count(*) from gtp_src); end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f19()'); drop function gtp_f19(); -- zero column table - pragma is generated, but the table pragma -- mechanism cannot to apply it (warning is expected) create function gtp_f20() returns void as $$ begin create temp table gtp_zero as select from gtp_src; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f20()'); drop function gtp_f20(); -- CREATE TABLE AS EXECUTE is ignored create function gtp_f21() returns void as $$ begin create temp table gtp_pe as execute some_prepared_stmt; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f21()'); drop function gtp_f21(); -- inner query can use plpgsql variables create function gtp_f22() returns void as $$ declare v_id int default 10; v_txt text default 'x'; begin create temp table gtp_vars as select v_id as id, v_txt as txt, a from gtp_src where a > v_id; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f22()'); drop function gtp_f22(); -- regprocedure argument selects one function from overloaded set, -- function can use OUT arguments create function gtp_over(p int) returns void as $$ begin create temp table gtp_o1 as select p as x; end; $$ language plpgsql; create function gtp_over(p text) returns void as $$ begin create temp table gtp_o2 as select p as y; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_over(int)'); select * from plpgsql_make_pragma('gtp_over(text)'); drop function gtp_over(int); drop function gtp_over(text); create function gtp_out(in p int, out o1 int, out o2 text) as $$ begin create temp table gtp_ot as select p + 1 as q; o1 := p; o2 := 'x'; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_out(int)'); drop function gtp_out(int); -- trigger function requires relid argument create table gtp_trg_tbl(id int, val text); create function gtp_trg() returns trigger as $$ begin create temp table gtp_trg_snapshot as select new.id, new.val; return new; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_trg()', 'gtp_trg_tbl'); drop function gtp_trg(); drop table gtp_trg_tbl; -- explicitly written pragmas are respected by generator create function gtp_f23() returns void as $$ begin execute 'create temp table gtp_dyn(a int, b text)'; perform plpgsql_check_pragma('table: gtp_dyn(a int, b text)'); create temp table gtp_from_dyn as select * from gtp_dyn; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_f23()'); drop function gtp_f23(); -- CREATE TABLE statement based forms. The temporary table is created -- inside the check's subtransaction (and removed by its rollback), and -- the pragma is derived from the structure of really created table. -- Column definition list form - typmods, quoting, serial, identity and -- generated columns, constraints, collation (not carried to pragma), -- zero columns create function gtp_cs1() returns void as $$ begin create temp table gtp_cl1 (id int, name text); create temp table gtp_cl2 (v varchar(10), n numeric(12,2), t text collate "C"); create temp table "GtpQ" ("select" int, normal text); create temp table gtp_cl3 (id serial, iid int generated always as identity, g int generated always as (id + 1) stored); create temp table gtp_cl4 (id int primary key, v text not null default 'x', check (id > 0)); create temp table gtp_cl5 (); end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_cs1()'); drop function gtp_cs1(); -- LIKE clause and OF type clause create function gtp_cs2() returns void as $$ begin create temp table gtp_lk1 (like gtp_src); create temp table gtp_lk2 (like gtp_src including all); create temp table gtp_lk3 (id int, like gtp_src); create temp table gtp_of1 of gtp_ct; create temp table gtp_of2 of gtp_ct (x with options not null); end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_cs2()'); drop function gtp_cs2(); -- inheritance - from a persistent table and from a temporary table -- created earlier in same function create table gtp_parent(pid int, pval text); create function gtp_cs3() returns void as $$ begin create temp table gtp_inh1 () inherits (gtp_parent); create temp table gtp_inh2 (own text) inherits (gtp_parent); create temp table gtp_tp (a int, b text); create temp table gtp_inh3 () inherits (gtp_tp); end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_cs3()'); drop function gtp_cs3(); drop table gtp_parent; -- partitioning - temporary parent and temporary partitions create function gtp_cs4() returns void as $$ begin create temp table gtp_pp (id int, v text) partition by range (id); create temp table gtp_p1 partition of gtp_pp for values from (1) to (100); create temp table gtp_p2 partition of gtp_pp default; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_cs4()'); drop function gtp_cs4(); -- storage options, access method, ON COMMIT variants, IF NOT EXISTS -- (the existing table wins - like runtime does), pg_temp qualification -- without TEMP keyword create function gtp_cs5() returns void as $$ begin create temp table gtp_o1 (a int) with (fillfactor = 70); create temp table gtp_o2 (a int) using heap; create temp table gtp_o3 (a int) on commit preserve rows; create temp table gtp_o4 (a int) on commit delete rows; create temp table gtp_o5 (a int) on commit drop; create temp table if not exists gtp_o6 (a int); create temp table if not exists gtp_o6 (b text); create table pg_temp.gtp_o7 (a int); create table pg_temp.gtp_o8 (like gtp_src); create table pg_temp.gtp_o9 of gtp_ct; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_cs5()'); drop function gtp_cs5(); -- mixed CREATE TABLE AS and CREATE TABLE statements create function gtp_mx1() returns void as $$ begin create temp table gtp_mm1 as select a, b from gtp_src; create temp table gtp_mm2 (id int, v text); end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_mx1()'); drop function gtp_mx1(); -- the table created by CREATE TABLE statement is visible for the -- following CREATE TABLE AS statements (join, subquery) create function gtp_mx2() returns void as $$ begin create temp table gtp_mn1 (a int, b text); create temp table gtp_mn2 as select gtp_mn1.a, s.b from gtp_mn1 join gtp_src s on s.a = gtp_mn1.a where gtp_mn1.a in (select a from gtp_mn1); end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_mx2()'); drop function gtp_mx2(); -- and the reverse order - the table created by CREATE TABLE AS -- statement is visible for the following CREATE TABLE statement create function gtp_mx3() returns void as $$ begin create temp table gtp_mo1 as select a, b from gtp_src; create temp table gtp_mo2 (like gtp_mo1 including all); end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_mx3()'); drop function gtp_mx3(); -- generator of CREATE TABLE statement based pragmas is idempotent too create function gtp_cs6() returns void as $$ begin create temp table gtp_ii (id int, v text); insert into gtp_ii values (1, 'x'); end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_cs6()'); select * from plpgsql_make_pragma('gtp_cs6()'); select count(*) from pg_class where relname in ('gtp_ii'); -- generated pragma is usable by "pragmas" option select * from plpgsql_check_function('gtp_cs6()', pragmas => array(select plpgsql_make_pragma('gtp_cs6()'))); drop function gtp_cs6(); -- error cases create function gtp_err1() returns void as $$ begin create temp table gtp_e1 as select * from gtp_nonexistent; end; $$ language plpgsql; -- generator reports same error like plpgsql_check_function select * from plpgsql_check_function('gtp_err1()'); \set VERBOSITY terse select * from plpgsql_make_pragma('gtp_err1()'); \set VERBOSITY default do $$ declare s text; m text; begin perform plpgsql_make_pragma('gtp_err1()'); exception when others then get stacked diagnostics s = returned_sqlstate, m = message_text; raise notice 'sqlstate: %, message: %', s, m; end; $$; drop function gtp_err1(); -- error is raised on first failed statement only, but when -- fatal_errors is false, then the scanning continues create function gtp_err2() returns void as $$ begin create temp table gtp_e21 as select * from gtp_missing1; create temp table gtp_e22 as select * from gtp_missing2; create temp table gtp_e23 as select a from gtp_src; end; $$ language plpgsql; do $$ declare s text; m text; begin perform plpgsql_make_pragma('gtp_err2()'); exception when others then get stacked diagnostics s = returned_sqlstate, m = message_text; raise notice 'sqlstate: %, message: %', s, m; end; $$; select * from plpgsql_make_pragma('gtp_err2()', fatal_errors => false); drop function gtp_err2(); -- temporary table cannot be created in non-temporary schema create function gtp_err3() returns void as $$ begin create temporary table public.gtp_e3 as select 1::int, 'JOHN'::text; end; $$ language plpgsql; do $$ declare s text; m text; begin perform plpgsql_make_pragma('gtp_err3()'); exception when others then get stacked diagnostics s = returned_sqlstate, m = message_text; raise notice 'sqlstate: %, message: %', s, m; end; $$; drop function gtp_err3(); -- the check of target schema is done before execution for CREATE TABLE -- statement forms too (column definition list, LIKE clause, OF type -- clause), and it respects fatal_errors option create function gtp_err4() returns void as $$ begin create temp table public.gtp_e41 (id int); create temp table public.gtp_e42 (like gtp_src); create temp table public.gtp_e43 of gtp_ct; create temp table gtp_e44 as select a from gtp_src; end; $$ language plpgsql; do $$ declare s text; m text; begin perform plpgsql_make_pragma('gtp_err4()'); exception when others then get stacked diagnostics s = returned_sqlstate, m = message_text; raise notice 'sqlstate: %, message: %', s, m; end; $$; select * from plpgsql_make_pragma('gtp_err4()', fatal_errors => false); drop function gtp_err4(); -- too many column names create function gtp_err5() returns void as $$ begin create temp table gtp_e5(c1, c2, c3) as select a, b from gtp_src; end; $$ language plpgsql; do $$ declare s text; m text; begin perform plpgsql_make_pragma('gtp_err5()'); exception when others then get stacked diagnostics s = returned_sqlstate, m = message_text; raise notice 'sqlstate: %, message: %', s, m; end; $$; drop function gtp_err5(); -- duplicate table name (usually the pattern CREATE, DROP, CREATE with -- same name) is reported as warning only - DROP statements are not -- executed by static scanning, so this is not a real error. Pragmas -- are returned for both definitions, and the following statements see -- the last definition. create function gtp_err6() returns void as $$ begin create temp table gtp_dd (x int); drop table gtp_dd; create temp table gtp_dd (y text); create temp table gtp_dd2 (z int); create temp table gtp_dd3 as select * from gtp_dd; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_err6()'); drop function gtp_err6(); -- mixed collisions - CREATE TABLE statement redefined by CREATE TABLE -- AS statement and vice versa create function gtp_err9() returns void as $$ begin create temp table gtp_md (a int); drop table gtp_md; create temp table gtp_md as select 'x'::text as t; create temp table gtp_md2 as select 1 as b; drop table gtp_md2; create temp table gtp_md2 (c date); create temp table gtp_md3 as select * from gtp_md, gtp_md2; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_err9()'); drop function gtp_err9(); -- CREATE TABLE AS IF NOT EXISTS - the existing table wins like in -- runtime create function gtp_err10() returns void as $$ begin create temp table gtp_ine2 as select 1 as a; create temp table if not exists gtp_ine2 as select 'x'::text as b; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_err10()'); drop function gtp_err10(); -- runtime errors of CREATE TABLE statement execution respect -- fatal_errors option - a temporary partition of persistent table create table gtp_ppart (id int) partition by range (id); create function gtp_err7() returns void as $$ begin create temp table gtp_bp partition of gtp_ppart for values from (1) to (100); create temp table gtp_bp2 (id int); end; $$ language plpgsql; do $$ declare s text; m text; begin perform plpgsql_make_pragma('gtp_err7()'); exception when others then get stacked diagnostics s = returned_sqlstate, m = message_text; raise notice 'sqlstate: %, message: %', s, m; end; $$; select * from plpgsql_make_pragma('gtp_err7()', fatal_errors => false); drop function gtp_err7(); drop table gtp_ppart; -- more not executable CREATE TABLE statements - foreign key from -- temporary table to persistent table, LIKE of missing table, OF -- missing type create function gtp_err8() returns void as $$ begin create temp table gtp_fk (id int references gtp_src(a)); create temp table gtp_bl (like gtp_not_exists); create temp table gtp_bo of gtp_type_not_exists; end; $$ language plpgsql; select * from plpgsql_make_pragma('gtp_err8()', fatal_errors => false); drop function gtp_err8(); -- "pragmas" option of check functions create function gtp_f24() returns void as $$ begin insert into gtp_manual values (1, 'x'); end; $$ language plpgsql; -- NULL and empty array have no effect select * from plpgsql_check_function('gtp_f24()', pragmas => null); select * from plpgsql_check_function('gtp_f24()', pragmas => '{}'::text[]); -- manually written (or fixed) pragma can be used, NULL elements -- are ignored select * from plpgsql_check_function('gtp_f24()', pragmas => array[null, 'table: gtp_manual(a int, b text)']); -- broken pragma raises warning only, and check continues select * from plpgsql_check_function('gtp_f24()', pragmas => array['table: gtp_manual(int int int)']); -- any pragma type is allowed select * from plpgsql_check_function('gtp_f24()', pragmas => array['disable: check']); drop function gtp_f24(); drop table gtp_src; drop type gtp_ct; drop domain gtp_dom;