-- Helper function which can be used for masking out portions of EXPLAIN -- ANALYZE which could contain information that's not consistent on all -- platforms. create function explain_analyze(query text) returns setof text
language plpgsql as
$$ declare
ln text;
begin for ln in
execute format('explain (analyze, costs off, summary off, timing off, buffers off) %s',
query) loop
ln := regexp_replace(ln, 'Maximum Storage: \d+', 'Maximum Storage: N'); return next ln;
end loop;
end;
$$;
-- Force generic plans to be used for all prepared statements in this file. set plan_cache_mode = force_generic_plan;
createtable lp (a char) partition by list (a); createtable lp_default partition of lp default; createtable lp_ef partition of lp forvaluesin ('e', 'f'); createtable lp_ad partition of lp forvaluesin ('a', 'd'); createtable lp_bc partition of lp forvaluesin ('b', 'c'); createtable lp_g partition of lp forvaluesin ('g'); createtable lp_null partition of lp forvaluesin (null); explain (costs off) select * from lp; explain (costs off) select * from lp where a > 'a'and a < 'd'; explain (costs off) select * from lp where a > 'a'and a <= 'd'; explain (costs off) select * from lp where a = 'a'; explain (costs off) select * from lp where'a' = a; /* commuted */ explain (costs off) select * from lp where a isnotnull; explain (costs off) select * from lp where a isnull; explain (costs off) select * from lp where a = 'a'or a = 'c'; explain (costs off) select * from lp where a isnotnulland (a = 'a'or a = 'c'); explain (costs off) select * from lp where a <> 'g'; explain (costs off) select * from lp where a <> 'a'and a <> 'd'; explain (costs off) select * from lp where a notin ('a', 'd');
-- collation matches the partitioning collation, pruning works createtable coll_pruning (a text collate"C") partition by list (a); createtable coll_pruning_a partition of coll_pruning forvaluesin ('a'); createtable coll_pruning_b partition of coll_pruning forvaluesin ('b'); createtable coll_pruning_def partition of coll_pruning default; explain (costs off) select * from coll_pruning where a collate"C" = 'a'collate"C"; -- collation doesn't match the partitioning collation, no pruning occurs explain (costs off) select * from coll_pruning where a collate"POSIX" = 'a'collate"POSIX";
createtable rlp (a int, b varchar) partition by range (a); createtable rlp_default partition of rlp default partition by list (a); createtable rlp_default_default partition of rlp_default default; createtable rlp_default_10 partition of rlp_default forvaluesin (10); createtable rlp_default_30 partition of rlp_default forvaluesin (30); createtable rlp_default_null partition of rlp_default forvaluesin (null); createtable rlp1 partition of rlp forvaluesfrom (minvalue) to (1); createtable rlp2 partition of rlp forvaluesfrom (1) to (10);
createtable rlp3 (b varchar, a int) partition by list (b varchar_ops); createtable rlp3_default partition of rlp3 default; createtable rlp3abcd partition of rlp3 forvaluesin ('ab', 'cd'); createtable rlp3efgh partition of rlp3 forvaluesin ('ef', 'gh'); createtable rlp3nullxy partition of rlp3 forvaluesin (null, 'xy'); altertable rlp attach partition rlp3 forvaluesfrom (15) to (20);
createtable rlp4 partition of rlp forvaluesfrom (20) to (30) partition by range (a); createtable rlp4_default partition of rlp4 default; createtable rlp4_1 partition of rlp4 forvaluesfrom (20) to (25); createtable rlp4_2 partition of rlp4 forvaluesfrom (25) to (29);
createtable rlp5 partition of rlp forvaluesfrom (31) to (maxvalue) partition by range (a); createtable rlp5_default partition of rlp5 default; createtable rlp5_1 partition of rlp5 forvaluesfrom (31) to (40);
explain (costs off) select * from rlp where a < 1; explain (costs off) select * from rlp where1 > a; /* commuted */ explain (costs off) select * from rlp where a <= 1; explain (costs off) select * from rlp where a = 1; explain (costs off) select * from rlp where a = 1::bigint; /* same as above */ explain (costs off) select * from rlp where a = 1::numeric; /* no pruning */ explain (costs off) select * from rlp where a <= 10; explain (costs off) select * from rlp where a > 10; explain (costs off) select * from rlp where a < 15; explain (costs off) select * from rlp where a <= 15; explain (costs off) select * from rlp where a > 15and b = 'ab'; explain (costs off) select * from rlp where a = 16; explain (costs off) select * from rlp where a = 16and b in ('not', 'in', 'here'); explain (costs off) select * from rlp where a = 16and b < 'ab'; explain (costs off) select * from rlp where a = 16and b <= 'ab'; explain (costs off) select * from rlp where a = 16and b isnull; explain (costs off) select * from rlp where a = 16and b isnotnull; explain (costs off) select * from rlp where a isnull; explain (costs off) select * from rlp where a isnotnull; explain (costs off) select * from rlp where a > 30; explain (costs off) select * from rlp where a = 30; /* only default is scanned */ explain (costs off) select * from rlp where a <= 31; explain (costs off) select * from rlp where a = 1or a = 7; explain (costs off) select * from rlp where a = 1or b = 'ab';
explain (costs off) select * from rlp where a > 20and a < 27; explain (costs off) select * from rlp where a = 29; explain (costs off) select * from rlp where a >= 29; explain (costs off) select * from rlp where a < 1or (a > 20and a < 25);
-- where clause contradicts sub-partition's constraint explain (costs off) select * from rlp where a = 20or a = 40; explain (costs off) select * from rlp3 where a = 20; /* empty */
-- redundant clauses are eliminated explain (costs off) select * from rlp where a > 1and a = 10; /* only default */ explain (costs off) select * from rlp where a > 1and a >=15; /* rlp3 onwards, including default */ explain (costs off) select * from rlp where a = 1and a = 3; /* empty */ explain (costs off) select * from rlp where (a = 1and a = 3) or (a > 1and a = 15);
-- multi-column keys createtable mc3p (a int, b int, c int) partition by range (a, abs(b), c); createtable mc3p_default partition of mc3p default; createtable mc3p0 partition of mc3p forvaluesfrom (minvalue, minvalue, minvalue) to (1, 1, 1); createtable mc3p1 partition of mc3p forvaluesfrom (1, 1, 1) to (10, 5, 10); createtable mc3p2 partition of mc3p forvaluesfrom (10, 5, 10) to (10, 10, 10); createtable mc3p3 partition of mc3p forvaluesfrom (10, 10, 10) to (10, 10, 20); createtable mc3p4 partition of mc3p forvaluesfrom (10, 10, 20) to (10, maxvalue, maxvalue); createtable mc3p5 partition of mc3p forvaluesfrom (11, 1, 1) to (20, 10, 10); createtable mc3p6 partition of mc3p forvaluesfrom (20, 10, 10) to (20, 20, 20); createtable mc3p7 partition of mc3p forvaluesfrom (20, 20, 20) to (maxvalue, maxvalue, maxvalue);
explain (costs off) select * from mc3p where a = 1; explain (costs off) select * from mc3p where a = 1and abs(b) < 1; explain (costs off) select * from mc3p where a = 1and abs(b) = 1; explain (costs off) select * from mc3p where a = 1and abs(b) = 1and c < 8; explain (costs off) select * from mc3p where a = 10and abs(b) between5and35; explain (costs off) select * from mc3p where a > 10; explain (costs off) select * from mc3p where a >= 10; explain (costs off) select * from mc3p where a < 10; explain (costs off) select * from mc3p where a <= 10and abs(b) < 10; explain (costs off) select * from mc3p where a = 11and abs(b) = 0; explain (costs off) select * from mc3p where a = 20and abs(b) = 10and c = 100; explain (costs off) select * from mc3p where a > 20; explain (costs off) select * from mc3p where a >= 20; explain (costs off) select * from mc3p where (a = 1and abs(b) = 1and c = 1) or (a = 10and abs(b) = 5andc = 10) or (a > 11and a < 20); explain (costs off) select * from mc3p where (a = 1and abs(b) = 1and c = 1) or (a = 10and abs(b) = 5andc = 10) or (a > 11and a < 20) or a < 1; explain (costs off) select * from mc3p where (a = 1and abs(b) = 1and c = 1) or (a = 10and abs(b) = 5andc = 10) or (a > 11and a < 20) or a < 1or a = 1; explain (costs off) select * from mc3p where a = 1or abs(b) = 1or c = 1; explain (costs off) select * from mc3p where (a = 1and abs(b) = 1) or (a = 10and abs(b) = 10); explain (costs off) select * from mc3p where (a = 1and abs(b) = 1) or (a = 10and abs(b) = 9);
-- a simpler multi-column keys case createtable mc2p (a int, b int) partition by range (a, b); createtable mc2p_default partition of mc2p default; createtable mc2p0 partition of mc2p forvaluesfrom (minvalue, minvalue) to (1, minvalue); createtable mc2p1 partition of mc2p forvaluesfrom (1, minvalue) to (1, 1); createtable mc2p2 partition of mc2p forvaluesfrom (1, 1) to (2, minvalue); createtable mc2p3 partition of mc2p forvaluesfrom (2, minvalue) to (2, 1); createtable mc2p4 partition of mc2p forvaluesfrom (2, 1) to (2, maxvalue); createtable mc2p5 partition of mc2p forvaluesfrom (2, maxvalue) to (maxvalue, maxvalue);
explain (costs off) select * from mc2p where a < 2; explain (costs off) select * from mc2p where a = 2and b < 1; explain (costs off) select * from mc2p where a > 1; explain (costs off) select * from mc2p where a = 1and b > 1;
-- all partitions but the default one should be pruned explain (costs off) select * from mc2p where a = 1and b isnull; explain (costs off) select * from mc2p where a isnulland b isnull; explain (costs off) select * from mc2p where a isnulland b = 1; explain (costs off) select * from mc2p where a isnull; explain (costs off) select * from mc2p where b isnull;
-- boolean partitioning createtable boolpart (a bool) partition by list (a); createtable boolpart_default partition of boolpart default; createtable boolpart_t partition of boolpart forvaluesin ('true'); createtable boolpart_f partition of boolpart forvaluesin ('false'); insertinto boolpart values (true), (false), (null);
explain (costs off) select * from boolpart where a in (true, false); explain (costs off) select * from boolpart where a = false; explain (costs off) select * from boolpart wherenot a = false; explain (costs off) select * from boolpart where a istrueor a isnottrue; explain (costs off) select * from boolpart where a isnottrue; explain (costs off) select * from boolpart where a isnottrueand a isnotfalse; explain (costs off) select * from boolpart where a is unknown; explain (costs off) select * from boolpart where a isnot unknown;
select * from boolpart where a in (true, false); select * from boolpart where a = false; select * from boolpart wherenot a = false; select * from boolpart where a istrueor a isnottrue; select * from boolpart where a isnottrue; select * from boolpart where a isnottrueand a isnotfalse; select * from boolpart where a is unknown; select * from boolpart where a isnot unknown;
-- try some other permutations with a NULL partition instead of a DEFAULT deletefrom boolpart where a isnull; createtable boolpart_null partition of boolpart forvaluesin (null); insertinto boolpart values(null);
explain (costs off) select * from boolpart where a isnottrue; explain (costs off) select * from boolpart where a isnottrueand a isnotfalse; explain (costs off) select * from boolpart where a isnotfalse; explain (costs off) select * from boolpart where a isnot unknown;
select * from boolpart where a isnottrue; select * from boolpart where a isnottrueand a isnotfalse; select * from boolpart where a isnotfalse; select * from boolpart where a isnot unknown;
-- check that all partitions are pruned when faced with conflicting clauses explain (costs off) select * from boolpart where a isnot unknown and a is unknown; explain (costs off) select * from boolpart where a isfalseand a is unknown; explain (costs off) select * from boolpart where a istrueand a is unknown;
-- inverse boolean partitioning - a seemingly unlikely design, but we've got -- code for it, so we'd better test it. createtable iboolpart (a bool) partition by list ((not a)); createtable iboolpart_default partition of iboolpart default; createtable iboolpart_f partition of iboolpart forvaluesin ('true'); createtable iboolpart_t partition of iboolpart forvaluesin ('false'); insertinto iboolpart values (true), (false), (null);
explain (costs off) select * from iboolpart where a in (true, false); explain (costs off) select * from iboolpart where a = false; explain (costs off) select * from iboolpart wherenot a = false; explain (costs off) select * from iboolpart where a istrueor a isnottrue; explain (costs off) select * from iboolpart where a isnottrue; explain (costs off) select * from iboolpart where a isnottrueand a isnotfalse; explain (costs off) select * from iboolpart where a is unknown; explain (costs off) select * from iboolpart where a isnot unknown;
select * from iboolpart where a in (true, false); select * from iboolpart where a = false; select * from iboolpart wherenot a = false; select * from iboolpart where a istrueor a isnottrue; select * from iboolpart where a isnottrue; select * from iboolpart where a isnottrueand a isnotfalse; select * from iboolpart where a is unknown; select * from iboolpart where a isnot unknown;
-- Try some other permutations with a NULL partition instead of a DEFAULT deletefrom iboolpart where a isnull; createtable iboolpart_null partition of iboolpart forvaluesin (null); insertinto iboolpart values(null);
-- Pruning shouldn't take place for these. Just check the result is correct select * from iboolpart where a isnottrue; select * from iboolpart where a isnottrueand a isnotfalse; select * from iboolpart where a isnotfalse;
createtable boolrangep (a bool, b bool, c int) partition by range (a,b,c); createtable boolrangep_tf partition of boolrangep forvaluesfrom ('true', 'false', 0) to ('true', 'false', 100); createtable boolrangep_ft partition of boolrangep forvaluesfrom ('false', 'true', 0) to ('false', 'true', 100); createtable boolrangep_ff1 partition of boolrangep forvaluesfrom ('false', 'false', 0) to('false', 'false', 50); createtable boolrangep_ff2 partition of boolrangep forvaluesfrom ('false', 'false', 50) to ('false', 'false', 100); createtable boolrangep_null partition of boolrangep default;
-- try a more complex case that's been known to trip up pruning in the past explain (costs off) select * from boolrangep wherenot a andnot b and c = 25;
-- ensure we prune boolrangep_tf explain (costs off) select * from boolrangep where a isnottrueandnot b and c = 25;
-- ensure we prune everything apart from boolrangep_tf and boolrangep_null explain (costs off) select * from boolrangep where a isnotfalseandnot b and c = 25;
-- test scalar-to-array operators createtable coercepart (a varchar) partition by list (a); createtable coercepart_ab partition of coercepart forvaluesin ('ab'); createtable coercepart_bc partition of coercepart forvaluesin ('bc'); createtable coercepart_cd partition of coercepart forvaluesin ('cd');
explain (costs off) select * from coercepart where a in ('ab', to_char(125, '999')); explain (costs off) select * from coercepart where a ~ any ('{ab}'); explain (costs off) select * from coercepart where a !~ all ('{ab}'); explain (costs off) select * from coercepart where a ~ any ('{ab,bc}'); explain (costs off) select * from coercepart where a !~ all ('{ab,bc}'); explain (costs off) select * from coercepart where a = any ('{ab,bc}'); explain (costs off) select * from coercepart where a = any ('{ab,null}'); explain (costs off) select * from coercepart where a = any (null::text[]); explain (costs off) select * from coercepart where a = all ('{ab}'); explain (costs off) select * from coercepart where a = all ('{ab,bc}'); explain (costs off) select * from coercepart where a = all ('{ab,null}'); explain (costs off) select * from coercepart where a = all (null::text[]);
droptable coercepart;
CREATETABLE part (a INT, b INT) PARTITION BY LIST (a); CREATETABLE part_p1 PARTITION OF part FORVALUESIN (-2,-1,0,1,2); CREATETABLE part_p2 PARTITION OF part DEFAULT PARTITION BY RANGE(a); CREATETABLE part_p2_p1 PARTITION OF part_p2 DEFAULT; CREATETABLE part_rev (b INT, c INT, a INT); ALTERTABLE part ATTACH PARTITION part_rev FORVALUESIN (3); -- fail ALTERTABLE part_rev DROPCOLUMN c; ALTERTABLE part ATTACH PARTITION part_rev FORVALUESIN (3); -- now it's ok INSERTINTO part VALUES (-1,-1), (1,1), (2,NULL), (NULL,-2),(NULL,NULL); EXPLAIN (COSTS OFF) SELECT tableoid::regclass as part, a, b FROM part WHERE a ISNULLORDERBY1, 2, 3; EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM part p(x) ORDERBY x;
-- -- some more cases --
-- -- pruning for partitioned table appearing inside a sub-query -- -- pruning won't work for mc3p, because some keys are Params explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = t1.b and abs(t2.b) = 1and t2.c = 1) s where t1.a = 1;
-- pruning should work fine, because values for a prefix of keys (a, b) are -- available explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.c = t1.b and abs(t2.b) = 1and t2.a = 1) s where t1.a = 1;
-- also here, because values for all keys are provided explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = 1and abs(t2.b) = 1and t2.c = 1) s where t1.a = 1;
-- -- pruning with clauses containing <> operator --
-- doesn't prune range partitions createtable rp (a int) partition by range (a); createtable rp0 partition of rp forvaluesfrom (minvalue) to (1); createtable rp1 partition of rp forvaluesfrom (1) to (2); createtable rp2 partition of rp forvaluesfrom (2) to (maxvalue);
explain (costs off) select * from rp where a <> 1; explain (costs off) select * from rp where a <> 1and a <> 2;
-- null partition should be eliminated due to strict <> clause. explain (costs off) select * from lp where a <> 'a';
-- ensure we detect contradictions in clauses; a can't be NULL and NOT NULL. explain (costs off) select * from lp where a <> 'a'and a isnull; explain (costs off) select * from lp where (a <> 'a'and a <> 'd') or a isnull;
-- check that it also works for a partitioned table that's not root, -- which in this case are partitions of rlp that are themselves -- list-partitioned on b explain (costs off) select * from rlp where a = 15and b <> 'ab'and b <> 'cd'and b <> 'xy'and b isnotnull;
-- -- different collations for different keys with same expression -- createtable coll_pruning_multi (a text) partition by range (substr(a, 1) collate"POSIX", substr(a, 1) collate"C"); createtable coll_pruning_multi1 partition of coll_pruning_multi forvaluesfrom ('a', 'a') to ('a', 'e'); createtable coll_pruning_multi2 partition of coll_pruning_multi forvaluesfrom ('a', 'e') to ('a', 'z'); createtable coll_pruning_multi3 partition of coll_pruning_multi forvaluesfrom ('b', 'a') to ('b', 'e');
-- no pruning, because no value for the leading key explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e'collate"C";
-- pruning, with a value provided for the leading key explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'a'collate"POSIX";
-- pruning, with values provided for both keys explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e'collate"C"and substr(a, 1) = 'a'collate"POSIX";
-- -- LIKE operators don't prune -- createtable like_op_noprune (a text) partition by list (a); createtable like_op_noprune1 partition of like_op_noprune forvaluesin ('ABC'); createtable like_op_noprune2 partition of like_op_noprune forvaluesin ('BCD'); explain (costs off) select * from like_op_noprune where a like'%BC';
-- -- tests wherein clause value requires a cross-type comparison function -- createtable lparted_by_int2 (a smallint) partition by list (a); createtable lparted_by_int2_1 partition of lparted_by_int2 forvaluesin (1); createtable lparted_by_int2_16384 partition of lparted_by_int2 forvaluesin (16384); explain (costs off) select * from lparted_by_int2 where a = 100_000_000_000_000;
createtable rparted_by_int2 (a smallint) partition by range (a); createtable rparted_by_int2_1 partition of rparted_by_int2 forvaluesfrom (1) to (10); createtable rparted_by_int2_16384 partition of rparted_by_int2 forvaluesfrom (10) to (16384); -- all partitions pruned explain (costs off) select * from rparted_by_int2 where a > 100_000_000_000_000; createtable rparted_by_int2_maxvalue partition of rparted_by_int2 forvaluesfrom (16384) to (maxvalue); -- all partitions but rparted_by_int2_maxvalue pruned explain (costs off) select * from rparted_by_int2 where a > 100_000_000_000_000;
-- check that AlternativeSubPlan within a pruning expression gets cleaned up
createtable asptab (id intprimarykey) partition by range (id); createtable asptab0 partition of asptab forvaluesfrom (0) to (1); createtable asptab1 partition of asptab forvaluesfrom (1) to (2);
explain (costs off) select * from
(selectexists (select1from int4_tbl tinner where f1 = touter.f1) as b from int4_tbl touter) ss,
asptab where asptab.id > ss.b::int;
droptable asptab;
-- -- Test Partition pruning for HASH partitioning -- -- Use hand-rolled hash functions and operator classes to get predictable -- result on different machines. See the definitions of -- part_test_int4_ops and part_test_text_ops in test_setup.sql. --
createtable hp (a int, b text, c int)
partition by hash (a part_test_int4_ops, b part_test_text_ops); createtable hp0 partition of hp forvalueswith (modulus 4, remainder 0); createtable hp3 partition of hp forvalueswith (modulus 4, remainder 3); createtable hp1 partition of hp forvalueswith (modulus 4, remainder 1); createtable hp2 partition of hp forvalueswith (modulus 4, remainder 2);
insertinto hp values (null, null, 0); insertinto hp values (1, null, 1); insertinto hp values (1, 'xxx', 2); insertinto hp values (null, 'xxx', 3); insertinto hp values (2, 'xxx', 4); insertinto hp values (1, 'abcde', 5); select tableoid::regclass, * from hp orderby c;
-- partial keys won't prune, nor would non-equality conditions explain (costs off) select * from hp where a = 1; explain (costs off) select * from hp where b = 'xxx'; explain (costs off) select * from hp where a isnull; explain (costs off) select * from hp where b isnull; explain (costs off) select * from hp where a < 1and b = 'xxx'; explain (costs off) select * from hp where a <> 1and b = 'yyy'; explain (costs off) select * from hp where a <> 1and b <> 'xxx';
-- pruning should work if either a value or a IS NULL clause is provided for -- each of the keys explain (costs off) select * from hp where a isnulland b isnull; explain (costs off) select * from hp where a = 1and b isnull; explain (costs off) select * from hp where a = 1and b = 'xxx'; explain (costs off) select * from hp where a isnulland b = 'xxx'; explain (costs off) select * from hp where a = 2and b = 'xxx'; explain (costs off) select * from hp where a = 1and b = 'abcde'; explain (costs off) select * from hp where (a = 1and b = 'abcde') or (a = 2and b = 'xxx') or (a isnulland b isnull);
-- test pruning when not all the partitions exist droptable hp1; droptable hp3; explain (costs off) select * from hp where a = 1and b = 'abcde'; explain (costs off) select * from hp where a = 1and b = 'abcde'and
(c = 2or c = 3); droptable hp2; explain (costs off) select * from hp where a = 1and b = 'abcde'and
(c = 2or c = 3);
-- -- Test runtime partition pruning -- createtable ab (a intnotnull, b intnotnull) partition by list (a); createtable ab_a2 partition of ab forvaluesin(2) partition by list (b); createtable ab_a2_b1 partition of ab_a2 forvaluesin (1); createtable ab_a2_b2 partition of ab_a2 forvaluesin (2); createtable ab_a2_b3 partition of ab_a2 forvaluesin (3); createtable ab_a1 partition of ab forvaluesin(1) partition by list (b); createtable ab_a1_b1 partition of ab_a1 forvaluesin (1); createtable ab_a1_b2 partition of ab_a1 forvaluesin (2); createtable ab_a1_b3 partition of ab_a1 forvaluesin (3); createtable ab_a3 partition of ab forvaluesin(3) partition by list (b); createtable ab_a3_b1 partition of ab_a3 forvaluesin (1); createtable ab_a3_b2 partition of ab_a3 forvaluesin (2); createtable ab_a3_b3 partition of ab_a3 forvaluesin (3);
-- Disallow index only scans as concurrent transactions may stop visibility -- bits being set causing "Heap Fetches" to be unstable in the EXPLAIN ANALYZE -- output. set enable_indexonlyscan = off;
prepare ab_q1 (int, int, int) as select * from ab where a between $1and $2and b <= $3;
-- Ensure a mix of PARAM_EXTERN and PARAM_EXEC Params work together at -- different levels of partitioning.
prepare ab_q2 (int, int) as select a from ab where a between $1and $2and b < (select3);
-- As above, but swap the PARAM_EXEC Param to the first partition level
prepare ab_q3 (int, int) as select a from ab where b between $1and $2and a < (select3);
-- -- Test runtime pruning with hash partitioned tables --
-- recreate partitions dropped above createtable hp1 partition of hp forvalueswith (modulus 4, remainder 1); createtable hp2 partition of hp forvalueswith (modulus 4, remainder 2); createtable hp3 partition of hp forvalueswith (modulus 4, remainder 3);
-- Ensure we correctly prune unneeded partitions when there is an IS NULL qual
prepare hp_q1 (text) as select * from hp where a isnulland b = $1;
explain (costs off) execute hp_q1('xxx');
deallocate hp_q1;
droptable hp;
-- Test a backwards Append scan createtable list_part (a int) partition by list (a); createtable list_part1 partition of list_part forvaluesin (1); createtable list_part2 partition of list_part forvaluesin (2); createtable list_part3 partition of list_part forvaluesin (3); createtable list_part4 partition of list_part forvaluesin (4);
insertinto list_part select generate_series(1,4);
begin;
-- Don't select an actual value out of the table as the order of the Append's -- subnodes may not be stable. declare cur SCROLL CURSORforselect1from list_part where a > (select1) and a < (select4);
-- move beyond the final row
move 3from cur;
-- Ensure we get two rows. fetch backward allfrom cur;
commit;
begin;
-- Test run-time pruning using stable functions create function list_part_fn(int) returns intas $$ begin return $1; end;$$ language plpgsql stable;
-- Ensure pruning works using a stable function containing no Vars explain (analyze, costs off, summary off, timing off, buffers off) select * from list_part where a = list_part_fn(1);
-- Ensure pruning does not take place when the function has a Var parameter explain (analyze, costs off, summary off, timing off, buffers off) select * from list_part where a = list_part_fn(a);
-- Ensure pruning does not take place when the expression contains a Var. explain (analyze, costs off, summary off, timing off, buffers off) select * from list_part where a = list_part_fn(1) + a;
rollback;
droptable list_part;
-- Parallel append
-- Parallel queries won't necessarily get as many workers as the planner -- asked for. This affects not only the "Workers Launched:" field of EXPLAIN -- results, but also row counts and loop counts for parallel scans, Gathers, -- and everything in between. This function filters out the values we can't -- rely on to be stable. -- This removes enough info that you might wonder why bother with EXPLAIN -- ANALYZE at all. The answer is that we need to see '(never executed)' -- notations because that's the only way to verify runtime pruning. create function explain_parallel_append(text) returns setof text
language plpgsql as
$$ declare
ln text;
begin for ln in
execute format('explain (analyze, costs off, summary off, timing off, buffers off) %s',
$1) loop
ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N');
ln := regexp_replace(ln, 'actual rows=\d+(?:\.\d+)? loops=\d+', 'actual rows=N loops=N');
ln := regexp_replace(ln, 'Rows Removed by Filter: \d+', 'Rows Removed by Filter: N');
perform regexp_matches(ln, 'Index Searches: \d+'); if found then continue;
end if; return next ln;
end loop;
end;
$$;
prepare ab_q4 (int, int) as select avg(a) from ab where a between $1and $2and b < 4;
-- Encourage use of parallel plans set parallel_setup_cost = 0; set parallel_tuple_cost = 0; set min_parallel_table_scan_size = 0; set max_parallel_workers_per_gather = 2;
-- Try some params whose values do not belong to any partition. select explain_parallel_append('execute ab_q5 (33, 44, 55)');
-- Test Parallel Append with PARAM_EXEC Params select explain_parallel_append('select count(*) from ab where (a = (select 1) or a = (select 3)) and b = 2');
-- Test pruning during parallel nested loop query createtable lprt_a (a intnotnull); -- Insert some values we won't find in ab insertinto lprt_a select0from generate_series(1,100);
-- and insert some values that we should find. insertinto lprt_a values(1),(1);
analyze lprt_a;
createindex ab_a2_b1_a_idx on ab_a2_b1 (a); createindex ab_a2_b2_a_idx on ab_a2_b2 (a); createindex ab_a2_b3_a_idx on ab_a2_b3 (a); createindex ab_a1_b1_a_idx on ab_a1_b1 (a); createindex ab_a1_b2_a_idx on ab_a1_b2 (a); createindex ab_a1_b3_a_idx on ab_a1_b3 (a); createindex ab_a3_b1_a_idx on ab_a3_b1 (a); createindex ab_a3_b2_a_idx on ab_a3_b2 (a); createindex ab_a3_b3_a_idx on ab_a3_b3 (a);
set enable_hashjoin = 0; set enable_mergejoin = 0; set enable_memoize = 0;
select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(0, 0, 1)');
-- Ensure the same partitions are pruned when we make the nested loop -- parameter an Expr rather than a plain Param. select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a + 0 where a.a in(0, 0, 1)');
insertinto lprt_a values(3),(3);
select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 3)'); select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)');
deletefrom lprt_a where a = 1;
select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)');
-- Test run-time partition pruning with an initplan explain (analyze, costs off, summary off, timing off, buffers off) select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1from lprt_a);
-- Test run-time partition pruning with UNION ALL parents explain (analyze, costs off, summary off, timing off, buffers off) select * from (select * from ab where a = 1unionallselect * from ab) ab where b = (select1);
-- A case containing a UNION ALL with a non-partitioned child. explain (analyze, costs off, summary off, timing off, buffers off) select * from (select * from ab where a = 1unionall (values(10,5)) unionallselect * from ab) ab where b = (select1);
-- Another UNION ALL test, but containing a mix of exec init and exec run-time pruning. createtable xy_1 (x int, y int); insertinto xy_1 values(100,-10);
set enable_bitmapscan = 0; set enable_indexscan = 0;
prepare ab_q6 as select * from ( select tableoid::regclass,a,b from ab unionall select tableoid::regclass,x,y from xy_1 unionall select tableoid::regclass,a,b from ab
) ab where a = $1and b = (select -10);
-- Ensure the xy_1 subplan is not pruned. explain (analyze, costs off, summary off, timing off, buffers off) execute ab_q6(1);
-- Ensure we see just the xy_1 row.
execute ab_q6(100);
-- UPDATE on a partition subtree has been seen to have problems. insertinto ab values (1,2); select explain_analyze(' update ab_a1 set b = 3from ab where ab.a = 1and ab.a = ab_a1.a;'); table ab;
-- Test UPDATE where source relation has run-time pruning enabled
truncate ab; insertinto ab values (1, 1), (1, 2), (1, 3), (2, 1); select explain_analyze(' update ab_a1 set b = 3from ab_a2 where ab_a2.b = (select1);'); select tableoid::regclass, * from ab;
-- Basic table createtable tprt (col1 int) partition by range (col1); createtable tprt_1 partition of tprt forvaluesfrom (1) to (501); createtable tprt_2 partition of tprt forvaluesfrom (501) to (1001); createtable tprt_3 partition of tprt forvaluesfrom (1001) to (2001); createtable tprt_4 partition of tprt forvaluesfrom (2001) to (3001); createtable tprt_5 partition of tprt forvaluesfrom (3001) to (4001); createtable tprt_6 partition of tprt forvaluesfrom (4001) to (5001);
createindex tprt1_idx on tprt_1 (col1); createindex tprt2_idx on tprt_2 (col1); createindex tprt3_idx on tprt_3 (col1); createindex tprt4_idx on tprt_4 (col1); createindex tprt5_idx on tprt_5 (col1); createindex tprt6_idx on tprt_6 (col1);
explain (analyze, costs off, summary off, timing off, buffers off) select * from tbl1 innerjoin tprt on tbl1.col1 = tprt.col1;
select tbl1.col1, tprt.col1 from tbl1 innerjoin tprt on tbl1.col1 > tprt.col1 orderby tbl1.col1, tprt.col1;
select tbl1.col1, tprt.col1 from tbl1 innerjoin tprt on tbl1.col1 = tprt.col1 orderby tbl1.col1, tprt.col1;
-- Last partition deletefrom tbl1; insertinto tbl1 values (4400); explain (analyze, costs off, summary off, timing off, buffers off) select * from tbl1 join tprt on tbl1.col1 < tprt.col1;
select tbl1.col1, tprt.col1 from tbl1 innerjoin tprt on tbl1.col1 < tprt.col1 orderby tbl1.col1, tprt.col1;
-- No matching partition deletefrom tbl1; insertinto tbl1 values (10000); explain (analyze, costs off, summary off, timing off, buffers off) select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
select tbl1.col1, tprt.col1 from tbl1 innerjoin tprt on tbl1.col1 = tprt.col1 orderby tbl1.col1, tprt.col1;
droptable tbl1, tprt;
-- Test with columns defined in varying orders between each level createtable part_abc (a intnotnull, b intnotnull, c intnotnull) partition by list (a); createtable part_bac (b intnotnull, a intnotnull, c intnotnull) partition by list (b); createtable part_cab (c intnotnull, a intnotnull, b intnotnull) partition by list (c); createtable part_abc_p1 (a intnotnull, b intnotnull, c intnotnull);
prepare part_abc_q1 (int, int, int) as select * from part_abc where a = $1and b = $2and c = $3;
-- Single partition should be scanned. explain (analyze, costs off, summary off, timing off, buffers off) execute part_abc_q1 (1, 2, 3);
deallocate part_abc_q1;
droptable part_abc;
-- Ensure that an Append node properly handles a sub-partitioned table -- matching without any of its leaf partitions matching the clause. createtable listp (a int, b int) partition by list (a); createtable listp_1 partition of listp forvaluesin(1) partition by list (b); createtable listp_1_1 partition of listp_1 forvaluesin(1); createtable listp_2 partition of listp forvaluesin(2) partition by list (b); createtable listp_2_1 partition of listp_2 forvaluesin(2); select * from listp where b = 1;
-- Ensure that an Append node properly can handle selection of all first level -- partitions before finally detecting the correct set of 2nd level partitions -- which match the given parameter.
prepare q1 (int,int) asselect * from listp where b in ($1,$2);
-- Try with no matching partitions. explain (analyze, costs off, summary off, timing off, buffers off) execute q1 (0,0);
deallocate q1;
-- Test more complex cases where a not-equal condition further eliminates partitions.
prepare q1 (int,int,int,int) asselect * from listp where b in($1,$2) and $3 <> b and $4 <> b;
-- Both partitions allowed by IN clause, but one disallowed by <> clause explain (analyze, costs off, summary off, timing off, buffers off) execute q1 (1,2,2,0);
-- Both partitions allowed by IN clause, then both excluded again by <> clauses. explain (analyze, costs off, summary off, timing off, buffers off) execute q1 (1,2,2,1);
-- Ensure Params that evaluate to NULL properly prune away all partitions explain (analyze, costs off, summary off, timing off, buffers off) select * from listp where a = (selectnull::int);
droptable listp;
-- -- check that stable query clauses are only used in run-time pruning -- createtable stable_qual_pruning (a timestamp) partition by range (a); createtable stable_qual_pruning1 partition of stable_qual_pruning forvaluesfrom ('2000-01-01') to ('2000-02-01'); createtable stable_qual_pruning2 partition of stable_qual_pruning forvaluesfrom ('2000-02-01') to ('2000-03-01'); createtable stable_qual_pruning3 partition of stable_qual_pruning forvaluesfrom ('3000-02-01') to ('3000-03-01');
-- comparison against a stable value requires run-time pruning explain (analyze, costs off, summary off, timing off, buffers off) select * from stable_qual_pruning where a < localtimestamp;
-- timestamp < timestamptz comparison is only stable, not immutable explain (analyze, costs off, summary off, timing off, buffers off) select * from stable_qual_pruning where a < '2000-02-01'::timestamptz;
-- check ScalarArrayOp cases explain (analyze, costs off, summary off, timing off, buffers off) select * from stable_qual_pruning where a = any(array['2010-02-01', '2020-01-01']::timestamp[]); explain (analyze, costs off, summary off, timing off, buffers off) select * from stable_qual_pruning where a = any(array['2000-02-01', '2010-01-01']::timestamp[]); explain (analyze, costs off, summary off, timing off, buffers off) select * from stable_qual_pruning where a = any(array['2000-02-01', localtimestamp]::timestamp[]); explain (analyze, costs off, summary off, timing off, buffers off) select * from stable_qual_pruning where a = any(array['2010-02-01', '2020-01-01']::timestamptz[]); explain (analyze, costs off, summary off, timing off, buffers off) select * from stable_qual_pruning where a = any(array['2000-02-01', '2010-01-01']::timestamptz[]); explain (analyze, costs off, summary off, timing off, buffers off) select * from stable_qual_pruning where a = any(null::timestamptz[]);
droptable stable_qual_pruning;
-- -- Check that pruning with composite range partitioning works correctly when -- it must ignore clauses for trailing keys once it has seen a clause with -- non-inclusive operator for an earlier key -- createtable mc3p (a int, b int, c int) partition by range (a, abs(b), c); createtable mc3p0 partition of mc3p forvaluesfrom (0, 0, 0) to (0, maxvalue, maxvalue); createtable mc3p1 partition of mc3p forvaluesfrom (1, 1, 1) to (2, minvalue, minvalue); createtable mc3p2 partition of mc3p forvaluesfrom (2, minvalue, minvalue) to (3, maxvalue, maxvalue); insertinto mc3p values (0, 1, 1), (1, 1, 1), (2, 1, 1);
explain (analyze, costs off, summary off, timing off, buffers off) select * from mc3p where a < 3and abs(b) = 1;
-- -- Check that pruning with composite range partitioning works correctly when -- a combination of runtime parameters is specified, not all of whose values -- are available at the same time --
prepare ps1 as select * from mc3p where a = $1and abs(b) < (select3); explain (analyze, costs off, summary off, timing off, buffers off)
execute ps1(1);
deallocate ps1;
prepare ps2 as select * from mc3p where a <= $1and abs(b) < (select3); explain (analyze, costs off, summary off, timing off, buffers off)
execute ps2(1);
deallocate ps2;
droptable mc3p;
-- Ensure runtime pruning works with initplans params with boolean types createtable boolvalues (value bool notnull); insertinto boolvalues values('t'),('f');
createtable boolp (a bool) partition by list (a); createtable boolp_t partition of boolp forvaluesin('t'); createtable boolp_f partition of boolp forvaluesin('f');
explain (analyze, costs off, summary off, timing off, buffers off) select * from boolp where a = (select value from boolvalues where value);
explain (analyze, costs off, summary off, timing off, buffers off) select * from boolp where a = (select value from boolvalues wherenot value);
droptable boolp;
-- -- Test run-time pruning of MergeAppend subnodes -- set enable_seqscan = off; set enable_sort = off; createtable ma_test (a int, b int) partition by range (a); createtable ma_test_p1 partition of ma_test forvaluesfrom (0) to (10); createtable ma_test_p2 partition of ma_test forvaluesfrom (10) to (20); createtable ma_test_p3 partition of ma_test forvaluesfrom (20) to (30); insertinto ma_test select x,x from generate_series(0,29) t(x); createindexon ma_test (b);
analyze ma_test;
prepare mt_q1 (int) asselect a from ma_test where a >= $1and a % 10 = 5orderby b;
-- -- check that pruning works properly when the partition key is of a -- pseudotype --
-- array type list partition key createtable pp_arrpart (a int[]) partition by list (a); createtable pp_arrpart1 partition of pp_arrpart forvaluesin ('{1}'); createtable pp_arrpart2 partition of pp_arrpart forvaluesin ('{2, 3}', '{4, 5}'); explain (costs off) select * from pp_arrpart where a = '{1}'; explain (costs off) select * from pp_arrpart where a = '{1, 2}'; explain (costs off) select * from pp_arrpart where a in ('{4, 5}', '{1}'); explain (costs off) update pp_arrpart set a = a where a = '{1}'; explain (costs off) deletefrom pp_arrpart where a = '{1}'; droptable pp_arrpart;
-- array type hash partition key createtable pph_arrpart (a int[]) partition by hash (a); createtable pph_arrpart1 partition of pph_arrpart forvalueswith (modulus 2, remainder 0); createtable pph_arrpart2 partition of pph_arrpart forvalueswith (modulus 2, remainder 1); insertinto pph_arrpart values ('{1}'), ('{1, 2}'), ('{4, 5}'); select tableoid::regclass, * from pph_arrpart orderby1; explain (costs off) select * from pph_arrpart where a = '{1}'; explain (costs off) select * from pph_arrpart where a = '{1, 2}'; explain (costs off) select * from pph_arrpart where a in ('{4, 5}', '{1}'); droptable pph_arrpart;
-- enum type list partition key create type pp_colors as enum ('green', 'blue', 'black'); createtable pp_enumpart (a pp_colors) partition by list (a); createtable pp_enumpart_green partition of pp_enumpart forvaluesin ('green'); createtable pp_enumpart_blue partition of pp_enumpart forvaluesin ('blue'); explain (costs off) select * from pp_enumpart where a = 'blue'; explain (costs off) select * from pp_enumpart where a = 'black'; droptable pp_enumpart; drop type pp_colors;
-- record type as partition key create type pp_rectype as (a int, b int); createtable pp_recpart (a pp_rectype) partition by list (a); createtable pp_recpart_11 partition of pp_recpart forvaluesin ('(1,1)'); createtable pp_recpart_23 partition of pp_recpart forvaluesin ('(2,3)'); explain (costs off) select * from pp_recpart where a = '(1,1)'::pp_rectype; explain (costs off) select * from pp_recpart where a = '(1,2)'::pp_rectype; droptable pp_recpart; drop type pp_rectype;
-- range type partition key createtable pp_intrangepart (a int4range) partition by list (a); createtable pp_intrangepart12 partition of pp_intrangepart forvaluesin ('[1,2]'); createtable pp_intrangepart2inf partition of pp_intrangepart forvaluesin ('[2,)'); explain (costs off) select * from pp_intrangepart where a = '[1,2]'::int4range; explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range; droptable pp_intrangepart;
createtable pp_lp (a int, value int) partition by list (a); createtable pp_lp1 partition of pp_lp forvaluesin(1); createtable pp_lp2 partition of pp_lp forvaluesin(2);
explain (costs off) select * from pp_lp where a = 1; explain (costs off) update pp_lp set value = 10where a = 1; explain (costs off) deletefrom pp_lp where a = 1;
set enable_partition_pruning = off;
set constraint_exclusion = 'partition'; -- this should not affect the result.
explain (costs off) select * from pp_lp where a = 1; explain (costs off) update pp_lp set value = 10where a = 1; explain (costs off) deletefrom pp_lp where a = 1;
set constraint_exclusion = 'off'; -- this should not affect the result.
explain (costs off) select * from pp_lp where a = 1; explain (costs off) update pp_lp set value = 10where a = 1; explain (costs off) deletefrom pp_lp where a = 1;
droptable pp_lp;
-- Ensure enable_partition_prune does not affect non-partitioned tables.
createtable inh_lp (a int, value int); createtable inh_lp1 (a int, value int, check(a = 1)) inherits (inh_lp); createtable inh_lp2 (a int, value int, check(a = 2)) inherits (inh_lp);
set constraint_exclusion = 'partition';
-- inh_lp2 should be removed in the following 3 cases. explain (costs off) select * from inh_lp where a = 1; explain (costs off) update inh_lp set value = 10where a = 1; explain (costs off) deletefrom inh_lp where a = 1;
-- Ensure we don't exclude normal relations when we only expect to exclude -- inheritance children explain (costs off) update inh_lp1 set value = 10where a = 2;
-- Check pruning for a partition tree containing only temporary relations create temp table pp_temp_parent (a int) partition by list (a); create temp table pp_temp_part_1 partition of pp_temp_parent forvaluesin (1); create temp table pp_temp_part_def partition of pp_temp_parent default; explain (costs off) select * from pp_temp_parent wheretrue; explain (costs off) select * from pp_temp_parent where a = 2; droptable pp_temp_parent;
-- Stress run-time partition pruning a bit more, per bug reports create temp table p (a int, b int, c int) partition by list (a); create temp table p1 partition of p forvaluesin (1); create temp table p2 partition of p forvaluesin (2); create temp table q (a int, b int, c int) partition by list (a); create temp table q1 partition of q forvaluesin (1) partition by list (b); create temp table q11 partition of q1 forvaluesin (1) partition by list (c); create temp table q111 partition of q11 forvaluesin (1); create temp table q2 partition of q forvaluesin (2) partition by list (b); create temp table q21 partition of q2 forvaluesin (1); create temp table q22 partition of q2 forvaluesin (2);
insertinto q22 values (2, 2, 3);
explain (costs off) select * from ( select * from p unionall select * from q1 unionall select1, 1, 1
) s(a, b, c) where s.a = 1and s.b = 1and s.c = (select1);
select * from ( select * from p unionall select * from q1 unionall select1, 1, 1
) s(a, b, c) where s.a = 1and s.b = 1and s.c = (select1);
prepare q (int, int) as select * from ( select * from p unionall select * from q1 unionall select1, 1, 1
) s(a, b, c) where s.a = $1and s.b = $2and s.c = (select1);
-- Ensure run-time pruning works correctly when we match a partitioned table -- on the first level but find no matching partitions on the second level. createtable listp (a int, b int) partition by list (a); createtable listp1 partition of listp forvaluesin(1); createtable listp2 partition of listp forvaluesin(2) partition by list(b); createtable listp2_10 partition of listp2 forvaluesin (10);
explain (analyze, costs off, summary off, timing off, buffers off) select * from listp where a = (select2) and b <> 10;
-- -- check that a partition directly accessed in a query is excluded with -- constraint_exclusion = on --
-- turn off partition pruning, so that it doesn't interfere set enable_partition_pruning to off;
-- setting constraint_exclusion to 'partition' disables exclusion set constraint_exclusion to'partition'; explain (costs off) select * from listp1 where a = 2; explain (costs off) update listp1 set a = 1where a = 2; -- constraint exclusion enabled set constraint_exclusion to'on'; explain (costs off) select * from listp1 where a = 2; explain (costs off) update listp1 set a = 1where a = 2;
-- Ensure run-time pruning works correctly for nested Append nodes set parallel_setup_cost to0; set parallel_tuple_cost to0;
createtable listp (a int) partition by list(a); createtable listp_12 partition of listp forvaluesin(1,2) partition by list(a); createtable listp_12_1 partition of listp_12 forvaluesin(1); createtable listp_12_2 partition of listp_12 forvaluesin(2);
-- Force the 2nd subnode of the Append to be non-parallel. This results in -- a nested Append node because the mixed parallel / non-parallel paths cannot -- be pulled into the top-level Append. altertable listp_12_1 set (parallel_workers = 0);
-- Ensure that listp_12_2 is not scanned. (The nested Append is not seen in -- the plan as it's pulled in setref.c due to having just a single subnode). select explain_parallel_append('select * from listp where a = (select 1);');
-- Like the above but throw some more complexity at the planner by adding -- a UNION ALL. We expect both sides of the union not to scan the -- non-required partitions. select explain_parallel_append( 'select * from listp where a = (select 1) unionall select * from listp where a = (select2);');
-- Test case for run-time pruning with a nested Merge Append set enable_sort to0; createtable rangep (a int, b int) partition by range (a); createtable rangep_0_to_100 partition of rangep forvaluesfrom (0) to (100) partition by list (b); -- We need 3 sub-partitions. 1 to validate pruning worked and another two -- because a single remaining partition would be pulled up to the main Append. createtable rangep_0_to_100_1 partition of rangep_0_to_100 forvaluesin(1); createtable rangep_0_to_100_2 partition of rangep_0_to_100 forvaluesin(2); createtable rangep_0_to_100_3 partition of rangep_0_to_100 forvaluesin(3); createtable rangep_100_to_200 partition of rangep forvaluesfrom (100) to (200); createindexon rangep (a);
-- Ensure run-time pruning works on the nested Merge Append explain (analyzeon, costs off, timing off, summary off, buffers off) select * from rangep where b IN((select1),(select2)) orderby a;
reset enable_sort; droptable rangep;
-- -- Check that gen_prune_steps_from_opexps() works well for various cases of -- clauses for different partition keys --
createtable rp_prefix_test1 (a int, b varchar) partition by range(a, b); createtable rp_prefix_test1_p1 partition of rp_prefix_test1 forvaluesfrom (1, 'a') to (1, 'b'); createtable rp_prefix_test1_p2 partition of rp_prefix_test1 forvaluesfrom (2, 'a') to (2, 'b');
-- Don't call get_steps_using_prefix() with the last partition key b plus -- an empty prefix explain (costs off) select * from rp_prefix_test1 where a <= 1and b = 'a';
createtable rp_prefix_test2 (a int, b int, c int) partition by range(a, b, c); createtable rp_prefix_test2_p1 partition of rp_prefix_test2 forvaluesfrom (1, 1, 0) to (1, 1, 10); createtable rp_prefix_test2_p2 partition of rp_prefix_test2 forvaluesfrom (2, 2, 0) to (2, 2, 10);
-- Don't call get_steps_using_prefix() with the last partition key c plus -- an invalid prefix (ie, b = 1) explain (costs off) select * from rp_prefix_test2 where a <= 1and b = 1and c >= 0;
createtable rp_prefix_test3 (a int, b int, c int, d int) partition by range(a, b, c, d); createtable rp_prefix_test3_p1 partition of rp_prefix_test3 forvaluesfrom (1, 1, 1, 0) to (1, 1, 1, 10); createtable rp_prefix_test3_p2 partition of rp_prefix_test3 forvaluesfrom (2, 2, 2, 0) to (2, 2, 2, 10);
-- Test that get_steps_using_prefix() handles a prefix that contains multiple -- clauses for the partition key b (ie, b >= 1 and b >= 2) explain (costs off) select * from rp_prefix_test3 where a >= 1and b >= 1and b >= 2and c >= 2and d >= 0;
-- Test that get_steps_using_prefix() handles a prefix that contains multiple -- clauses for the partition key b (ie, b >= 1 and b = 2) (This also tests -- that the caller arranges clauses in that prefix in the required order) explain (costs off) select * from rp_prefix_test3 where a >= 1and b >= 1and b = 2and c = 2and d >= 0;
-- -- Test that get_steps_using_prefix() handles IS NULL clauses correctly -- createtable hp_prefix_test (a int, b int, c int, d int)
partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops);
-- create 8 partitions select'create table hp_prefix_test_p' || x::text || ' partition of hp_prefix_test for values with (modulus 8, remainder ' || x::text || ');' from generate_Series(0,7) x;
\gexec
-- insert 16 rows, one row for each test to perform. insertinto hp_prefix_test select case a when0thennullelse1 end, case b when0thennullelse2 end, case c when0thennullelse3 end, case d when0thennullelse4 end from
generate_series(0,1) a,
generate_series(0,1) b,
generate_Series(0,1) c,
generate_Series(0,1) d;
-- Ensure partition pruning works correctly for each combination of IS NULL -- and equality quals. This may seem a little excessive, but there have been -- a number of bugs in this area over the years. We make use of row only -- output to reduce the size of the expected results.
\t on select 'explain (costs off) select tableoid::regclass,* from hp_prefix_test where ' ||
string_agg(c.colname || casewhen g.s & (1 << c.colpos) = 0then' is null'else' = ' || (colpos+1)::text end, ' and 'orderby c.colpos) from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s) groupby g.s orderby g.s;
\gexec
-- And ensure we get exactly 1 row from each. Again, all 16 possible combinations. select 'select tableoid::regclass,* from hp_prefix_test where ' ||
string_agg(c.colname || casewhen g.s & (1 << c.colpos) = 0then' is null'else' = ' || (colpos+1)::text end, ' and 'orderby c.colpos) from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s) groupby g.s orderby g.s;
\gexec
\t off
droptable hp_prefix_test;
-- -- Check that gen_partprune_steps() detects self-contradiction from clauses -- regardless of the order of the clauses (Here we use a custom operator to -- prevent the equivclass.c machinery from reordering the clauses) --
create operator === (
leftarg = int4,
rightarg = int4, procedure = int4eq,
commutator = ===,
hashes
); create operator class part_test_int4_ops2 for type int4 using hash as
operator 1 ===,
function 2 part_hashint4_noop(int4, int8);
createtable hp_contradict_test (a int, b int) partition by hash (a part_test_int4_ops2, b part_test_int4_ops2); createtable hp_contradict_test_p1 partition of hp_contradict_test forvalueswith (modulus 2, remainder 0); createtable hp_contradict_test_p2 partition of hp_contradict_test forvalueswith (modulus 2, remainder 1);
explain (costs off) select * from hp_contradict_test where a isnulland a === 1and b === 1; explain (costs off) select * from hp_contradict_test where a === 1and b === 1and a isnull;
droptable hp_contradict_test; drop operator class part_test_int4_ops2 using hash; drop operator ===(int4, int4);
drop function explain_analyze(text);
-- Runtime pruning on UPDATE using WITH CHECK OPTIONS and RETURNING createtable part_abc (a int, b text, c bool) partition by list (a); createtable part_abc_1 (b text, a int, c bool); createtable part_abc_2 (a int, c bool, b text); altertable part_abc attach partition part_abc_1 forvaluesin (1); altertable part_abc attach partition part_abc_2 forvaluesin (2); insertinto part_abc values (1, 'b', true); insertinto part_abc values (2, 'c', true); create view part_abc_view asselect * from part_abc where b <> 'a'withcheckoption;
prepare update_part_abc_view asupdate part_abc_view set b = $2where a = $1 returning *; -- Only the unpruned partition should be shown in the list of relations to be -- updated explain (verbose, costs off) execute update_part_abc_view (1, 'd');
execute update_part_abc_view (1, 'd'); explain (verbose, costs off) execute update_part_abc_view (2, 'a');
execute update_part_abc_view (2, 'a'); -- All pruned. explain (verbose, costs off) execute update_part_abc_view (3, 'a');
execute update_part_abc_view (3, 'a');
deallocate update_part_abc_view;
-- Runtime pruning on MERGE using a stable function create function stable_one() returns intas $$ begin return1; end; $$ language plpgsql stable; explain (costs off)
merge into part_abc_view pt using (select stable_one() as pid) as q join part_abc_1 pt1 on (q.pid = pt1.a) on pt.a = pt1.a when matched thendelete returning pt.a;
merge into part_abc_view pt using (select stable_one() as pid) as q join part_abc_1 pt1 on (q.pid = pt1.a) on pt.a = pt1.a when matched thendelete returning pt.a; table part_abc_view;
-- All pruned. explain (costs off)
merge into part_abc_view pt using (select stable_one() + 2as pid) as q join part_abc_1 pt1 on (q.pid = pt1.a) on pt.a = pt1.a when matched thendelete returning pt.a;
merge into part_abc_view pt using (select stable_one() + 2as pid) as q join part_abc_1 pt1 on (q.pid = pt1.a) on pt.a = pt1.a when matched thendelete returning pt.a; table part_abc_view;
-- MERGE ... INSERT when all pruned from MERGE source.
begin; explain (costs off)
merge into part_abc_view pt using (select stable_one() + 1as pid) as q join part_abc_2 pt2 on (q.pid = pt2.a) on pt.a = stable_one() + 2 whennot matched theninsertvalues (1, 'd', false) returning pt.a;
merge into part_abc_view pt using (select stable_one() + 1as pid) as q join part_abc_2 pt2 on (q.pid = pt2.a) on pt.a = stable_one() + 2 whennot matched theninsertvalues (1, 'd', false) returning pt.a; table part_abc_view;
rollback;
-- A case with multiple ModifyTable nodes.
begin; createtable part_abc_log (action text, a int, b text, c bool); explain (costs off) with t as (
merge into part_abc_view pt using (select stable_one() + 1as pid) as q join part_abc_2 pt2 on (q.pid = pt2.a) on pt.a = stable_one() + 2 whennot matched theninsertvalues (1, 'd', false) returning merge_action(), pt.*
) insertinto part_abc_log select * from t returning *; with t as (
merge into part_abc_view pt using (select stable_one() + 1as pid) as q join part_abc_2 pt2 on (q.pid = pt2.a) on pt.a = stable_one() + 2 whennot matched theninsertvalues (1, 'd', false) returning merge_action(), pt.*
) insertinto part_abc_log select * from t returning *; table part_abc_view; table part_abc_log;
rollback;
-- A case with nested MergeAppend with its own PartitionPruneInfo. createindexon part_abc (a); altertable part_abc add d int; createtable part_abc_3 partition of part_abc forvaluesin (3, 4) partition by range (d); createtable part_abc_3_1 partition of part_abc_3 forvaluesfrom (minvalue) to (1); createtable part_abc_3_2 partition of part_abc_3 forvaluesfrom (1) to (100); createtable part_abc_3_3 partition of part_abc_3 forvaluesfrom (100) to (maxvalue); explain (costs off) select min(a) over (partition by a orderby a) from part_abc where a >= stable_one() + 1and d <= stable_one() unionall select min(a) over (partition by a orderby a) from part_abc where a >= stable_one() + 1and d >= stable_one();
drop view part_abc_view; droptable part_abc;
-- -- Check that operands wrapped in PlaceHolderVars are matched to partition -- keys, allowing partition pruning to occur. PlaceHolderVars can be -- introduced when a subquery's output is used with grouping sets. -- createtable phv_part (a int, b text) partition by list (a); createtable phv_part_1 partition of phv_part forvaluesin (1); createtable phv_part_2 partition of phv_part forvaluesin (2); createtable phv_part_null partition of phv_part forvaluesin (null); insertinto phv_part values (1, 'one'), (2, 'two'), (null, 'null');
-- OpExpr: PHV-wrapped operand matched via equal() explain (costs off) select * from (select a, b from phv_part) t where a = 1 groupby grouping sets (a, b);
select * from (select a, b from phv_part) t where a = 1 groupby grouping sets (a, b);
-- OpExpr with RelabelType: PHV wrapped around a casted column explain (costs off) select * from (select a::oid as x, b from phv_part) t where x::int = 1 groupby grouping sets (x, b);
select * from (select a::oid as x, b from phv_part) t where x::int = 1 groupby grouping sets (x, b);
-- ScalarArrayOpExpr: IN clause with PHV-wrapped operand explain (costs off) select * from (select a, b from phv_part) t where a in (1, null) groupby grouping sets (a, b);
select * from (select a, b from phv_part) t where a in (1, null) groupby grouping sets (a, b);
-- NullTest: IS NULL with PHV-wrapped operand explain (costs off) select * from (select a, b from phv_part) t where a isnull groupby grouping sets (a, b);
select * from (select a, b from phv_part) t where a isnull groupby grouping sets (a, b);
droptable phv_part;
-- BooleanTest: IS TRUE with PHV-wrapped boolean partition key createtable phv_boolpart (a bool, b text) partition by list (a); createtable phv_boolpart_t partition of phv_boolpart forvaluesin (true); createtable phv_boolpart_f partition of phv_boolpart forvaluesin (false); createtable phv_boolpart_null partition of phv_boolpart default; insertinto phv_boolpart values (true, 'yes'), (false, 'no'), (null, 'unknown');
explain (costs off) select * from (select a, b from phv_boolpart) t where a istrue groupby grouping sets (a, b);
select * from (select a, b from phv_boolpart) t where a istrue groupby grouping sets (a, b);
droptable phv_boolpart;
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.62 Sekunden
(vorverarbeitet am 2026-08-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.