-- -- Tests to exercise the plan caching/invalidation mechanism -- CREATE TEMP TABLE pcachetest AS SELECT * FROM int8_tbl; -- create and use a cached plan PREPARE prepstmt AS SELECT * FROM pcachetest; EXECUTE prepstmt; q1 | q2 ------------------+------------------- 123 | 456 123 | 4567890123456789 4567890123456789 | 123 4567890123456789 | 4567890123456789 4567890123456789 | -4567890123456789 (5 rows) -- and one with parameters PREPARE prepstmt2(bigint) AS SELECT * FROM pcachetest WHERE q1 = $1; EXECUTE prepstmt2(123); q1 | q2 -----+------------------ 123 | 456 123 | 4567890123456789 (2 rows) -- invalidate the plans and see what happens DROP TABLE pcachetest; EXECUTE prepstmt; ERROR: relation "pcachetest" does not exist EXECUTE prepstmt2(123); ERROR: relation "pcachetest" does not exist -- recreate the temp table (this demonstrates that the raw plan is -- purely textual and doesn't depend on OIDs, for instance) CREATE TEMP TABLE pcachetest AS SELECT * FROM int8_tbl ORDER BY 2; EXECUTE prepstmt; q1 | q2 ------------------+------------------- 4567890123456789 | -4567890123456789 4567890123456789 | 123 123 | 456 123 | 4567890123456789 4567890123456789 | 4567890123456789 (5 rows) EXECUTE prepstmt2(123); q1 | q2 -----+------------------ 123 | 456 123 | 4567890123456789 (2 rows) -- prepared statements should prevent change in output tupdesc, -- since clients probably aren't expecting that to change on the fly ALTER TABLE pcachetest ADD COLUMN q3 bigint; EXECUTE prepstmt; ERROR: cached plan must not change result type EXECUTE prepstmt2(123); ERROR: cached plan must not change result type -- but we're nice guys and will let you undo your mistake ALTER TABLE pcachetest DROP COLUMN q3; EXECUTE prepstmt; q1 | q2 ------------------+------------------- 4567890123456789 | -4567890123456789 4567890123456789 | 123 123 | 456 123 | 4567890123456789 4567890123456789 | 4567890123456789 (5 rows) EXECUTE prepstmt2(123); q1 | q2 -----+------------------ 123 | 456 123 | 4567890123456789 (2 rows) -- Try it with a view, which isn't directly used in the resulting plan -- but should trigger invalidation anyway CREATE TEMP VIEW pcacheview AS SELECT * FROM pcachetest; PREPARE vprep AS SELECT * FROM pcacheview; EXECUTE vprep; q1 | q2 ------------------+------------------- 4567890123456789 | -4567890123456789 4567890123456789 | 123 123 | 456 123 | 4567890123456789 4567890123456789 | 4567890123456789 (5 rows) CREATE OR REPLACE TEMP VIEW pcacheview AS SELECT q1, q2/2 AS q2 FROM pcachetest; EXECUTE vprep; q1 | q2 ------------------+------------------- 4567890123456789 | -2283945061728394 4567890123456789 | 61 123 | 228 123 | 2283945061728394 4567890123456789 | 2283945061728394 (5 rows) -- Check basic SPI plan invalidation create function cache_test(int) returns int as $$ declare total int; begin create temp table t1(f1 int); insert into t1 values($1); insert into t1 values(11); insert into t1 values(12); insert into t1 values(13); select sum(f1) into total from t1; drop table t1; return total; end $$ language plpgsql; select cache_test(1); cache_test ------------ 37 (1 row) select cache_test(2); cache_test ------------ 38 (1 row) select cache_test(3); cache_test ------------ 39 (1 row) -- Check invalidation of plpgsql "simple expression" create temp view v1 as select 2+2 as f1; create function cache_test_2() returns int as $$ begin return f1 from v1; end$$ language plpgsql; select cache_test_2(); cache_test_2 -------------- 4 (1 row) create or replace temp view v1 as select 2+2+4 as f1; select cache_test_2(); cache_test_2 -------------- 8 (1 row) create or replace temp view v1 as select 2+2+4+(select max(unique1) from tenk1) as f1; select cache_test_2(); cache_test_2 -------------- 10007 (1 row) --- Check that change of search_path is honored when re-using cached plan create schema s1 create table abc (f1 int); create schema s2 create table abc (f1 int); insert into s1.abc values(123); insert into s2.abc values(456); set search_path = s1; prepare p1 as select f1 from abc; execute p1; f1 ----- 123 (1 row) set search_path = s2; select f1 from abc; f1 ----- 456 (1 row) execute p1; f1 ----- 456 (1 row) alter table s1.abc add column f2 float8; -- force replan execute p1; f1 ----- 456 (1 row) drop schema s1 cascade; NOTICE: drop cascades to table s1.abc drop schema s2 cascade; NOTICE: drop cascades to table abc reset search_path; -- Check that invalidation deals with regclass constants create temp sequence seq; prepare p2 as select nextval('seq'); execute p2; nextval --------- 1 (1 row) drop sequence seq; create temp sequence seq; execute p2; nextval --------- 1 (1 row) -- Check DDL via SPI, immediately followed by SPI plan re-use -- (bug in original coding) create function cachebug() returns void as $$ declare r int; begin drop table if exists temptable cascade; create temp table temptable as select * from generate_series(1,3) as f1; create temp view vv as select * from temptable; for r in select * from vv loop raise notice '%', r; end loop; end$$ language plpgsql; select cachebug(); NOTICE: table "temptable" does not exist, skipping NOTICE: 1 NOTICE: 2 NOTICE: 3 cachebug ---------- (1 row) select cachebug(); NOTICE: drop cascades to view vv NOTICE: 1 NOTICE: 2 NOTICE: 3 cachebug ---------- (1 row) -- Check that addition or removal of any partition is correctly dealt with by -- default partition table when it is being used in prepared statement. create table pc_list_parted (a int) partition by list(a); create table pc_list_part_null partition of pc_list_parted for values in (null); create table pc_list_part_1 partition of pc_list_parted for values in (1); create table pc_list_part_def partition of pc_list_parted default; prepare pstmt_def_insert (int) as insert into pc_list_part_def values($1); -- should fail execute pstmt_def_insert(null); ERROR: new row for relation "pc_list_part_def" violates partition constraint DETAIL: Failing row contains (null). execute pstmt_def_insert(1); ERROR: new row for relation "pc_list_part_def" violates partition constraint DETAIL: Failing row contains (1). create table pc_list_part_2 partition of pc_list_parted for values in (2); execute pstmt_def_insert(2); ERROR: new row for relation "pc_list_part_def" violates partition constraint DETAIL: Failing row contains (2). alter table pc_list_parted detach partition pc_list_part_null; -- should be ok execute pstmt_def_insert(null); drop table pc_list_part_1; -- should be ok execute pstmt_def_insert(1); drop table pc_list_parted, pc_list_part_null; deallocate pstmt_def_insert; -- Test plan_cache_mode create table test_mode (a int); insert into test_mode select 1 from generate_series(1,1000) union all select 2; create index on test_mode (a); analyze test_mode; prepare test_mode_pp (int) as select count(*) from test_mode where a = $1; select name, generic_plans, custom_plans from pg_prepared_statements where name = 'test_mode_pp'; name | generic_plans | custom_plans --------------+---------------+-------------- test_mode_pp | 0 | 0 (1 row) -- up to 5 executions, custom plan is used set plan_cache_mode to auto; explain (costs off) execute test_mode_pp(2); QUERY PLAN ---------------------------------------------------------- Aggregate -> Index Only Scan using test_mode_a_idx on test_mode Index Cond: (a = 2) (3 rows) select name, generic_plans, custom_plans from pg_prepared_statements where name = 'test_mode_pp'; name | generic_plans | custom_plans --------------+---------------+-------------- test_mode_pp | 0 | 1 (1 row) -- force generic plan set plan_cache_mode to force_generic_plan; explain (costs off) execute test_mode_pp(2); QUERY PLAN ----------------------------- Aggregate -> Seq Scan on test_mode Filter: (a = $1) (3 rows) select name, generic_plans, custom_plans from pg_prepared_statements where name = 'test_mode_pp'; name | generic_plans | custom_plans --------------+---------------+-------------- test_mode_pp | 1 | 1 (1 row) -- get to generic plan by 5 executions set plan_cache_mode to auto; execute test_mode_pp(1); -- 1x count ------- 1000 (1 row) execute test_mode_pp(1); -- 2x count ------- 1000 (1 row) execute test_mode_pp(1); -- 3x count ------- 1000 (1 row) execute test_mode_pp(1); -- 4x count ------- 1000 (1 row) select name, generic_plans, custom_plans from pg_prepared_statements where name = 'test_mode_pp'; name | generic_plans | custom_plans --------------+---------------+-------------- test_mode_pp | 1 | 5 (1 row) execute test_mode_pp(1); -- 5x count ------- 1000 (1 row) select name, generic_plans, custom_plans from pg_prepared_statements where name = 'test_mode_pp'; name | generic_plans | custom_plans --------------+---------------+-------------- test_mode_pp | 2 | 5 (1 row) -- we should now get a really bad plan explain (costs off) execute test_mode_pp(2); QUERY PLAN ----------------------------- Aggregate -> Seq Scan on test_mode Filter: (a = $1) (3 rows) -- but we can force a custom plan set plan_cache_mode to force_custom_plan; explain (costs off) execute test_mode_pp(2); QUERY PLAN ---------------------------------------------------------- Aggregate -> Index Only Scan using test_mode_a_idx on test_mode Index Cond: (a = 2) (3 rows) select name, generic_plans, custom_plans from pg_prepared_statements where name = 'test_mode_pp'; name | generic_plans | custom_plans --------------+---------------+-------------- test_mode_pp | 3 | 6 (1 row) drop table test_mode;