-- -- Test partitioning planner code -- -- Force generic plans to be used for all prepared statements in this file. set plan_cache_mode = force_generic_plan; create table lp (a char) partition by list (a); create table lp_default partition of lp default; create table lp_ef partition of lp for values in ('e', 'f'); create table lp_ad partition of lp for values in ('a', 'd'); create table lp_bc partition of lp for values in ('b', 'c'); create table lp_g partition of lp for values in ('g'); create table lp_null partition of lp for values in (null); explain (costs off) select * from lp; QUERY PLAN ----------------------------------- Append -> Seq Scan on lp_ad lp_1 -> Seq Scan on lp_bc lp_2 -> Seq Scan on lp_ef lp_3 -> Seq Scan on lp_g lp_4 -> Seq Scan on lp_null lp_5 -> Seq Scan on lp_default lp_6 (7 rows) explain (costs off) select * from lp where a > 'a' and a < 'd'; QUERY PLAN ----------------------------------------------------------- Append -> Seq Scan on lp_bc lp_1 Filter: ((a > 'a'::bpchar) AND (a < 'd'::bpchar)) -> Seq Scan on lp_default lp_2 Filter: ((a > 'a'::bpchar) AND (a < 'd'::bpchar)) (5 rows) explain (costs off) select * from lp where a > 'a' and a <= 'd'; QUERY PLAN ------------------------------------------------------------ Append -> Seq Scan on lp_ad lp_1 Filter: ((a > 'a'::bpchar) AND (a <= 'd'::bpchar)) -> Seq Scan on lp_bc lp_2 Filter: ((a > 'a'::bpchar) AND (a <= 'd'::bpchar)) -> Seq Scan on lp_default lp_3 Filter: ((a > 'a'::bpchar) AND (a <= 'd'::bpchar)) (7 rows) explain (costs off) select * from lp where a = 'a'; QUERY PLAN ----------------------------- Seq Scan on lp_ad lp Filter: (a = 'a'::bpchar) (2 rows) explain (costs off) select * from lp where 'a' = a; /* commuted */ QUERY PLAN ----------------------------- Seq Scan on lp_ad lp Filter: ('a'::bpchar = a) (2 rows) explain (costs off) select * from lp where a is not null; QUERY PLAN ----------------------------------- Append -> Seq Scan on lp_ad lp_1 Filter: (a IS NOT NULL) -> Seq Scan on lp_bc lp_2 Filter: (a IS NOT NULL) -> Seq Scan on lp_ef lp_3 Filter: (a IS NOT NULL) -> Seq Scan on lp_g lp_4 Filter: (a IS NOT NULL) -> Seq Scan on lp_default lp_5 Filter: (a IS NOT NULL) (11 rows) explain (costs off) select * from lp where a is null; QUERY PLAN ------------------------ Seq Scan on lp_null lp Filter: (a IS NULL) (2 rows) explain (costs off) select * from lp where a = 'a' or a = 'c'; QUERY PLAN ---------------------------------------------------------- Append -> Seq Scan on lp_ad lp_1 Filter: ((a = 'a'::bpchar) OR (a = 'c'::bpchar)) -> Seq Scan on lp_bc lp_2 Filter: ((a = 'a'::bpchar) OR (a = 'c'::bpchar)) (5 rows) explain (costs off) select * from lp where a is not null and (a = 'a' or a = 'c'); QUERY PLAN -------------------------------------------------------------------------------- Append -> Seq Scan on lp_ad lp_1 Filter: ((a IS NOT NULL) AND ((a = 'a'::bpchar) OR (a = 'c'::bpchar))) -> Seq Scan on lp_bc lp_2 Filter: ((a IS NOT NULL) AND ((a = 'a'::bpchar) OR (a = 'c'::bpchar))) (5 rows) explain (costs off) select * from lp where a <> 'g'; QUERY PLAN ------------------------------------ Append -> Seq Scan on lp_ad lp_1 Filter: (a <> 'g'::bpchar) -> Seq Scan on lp_bc lp_2 Filter: (a <> 'g'::bpchar) -> Seq Scan on lp_ef lp_3 Filter: (a <> 'g'::bpchar) -> Seq Scan on lp_default lp_4 Filter: (a <> 'g'::bpchar) (9 rows) explain (costs off) select * from lp where a <> 'a' and a <> 'd'; QUERY PLAN ------------------------------------------------------------- Append -> Seq Scan on lp_bc lp_1 Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) -> Seq Scan on lp_ef lp_2 Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) -> Seq Scan on lp_g lp_3 Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) -> Seq Scan on lp_default lp_4 Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) (9 rows) explain (costs off) select * from lp where a not in ('a', 'd'); QUERY PLAN ------------------------------------------------ Append -> Seq Scan on lp_bc lp_1 Filter: (a <> ALL ('{a,d}'::bpchar[])) -> Seq Scan on lp_ef lp_2 Filter: (a <> ALL ('{a,d}'::bpchar[])) -> Seq Scan on lp_g lp_3 Filter: (a <> ALL ('{a,d}'::bpchar[])) -> Seq Scan on lp_default lp_4 Filter: (a <> ALL ('{a,d}'::bpchar[])) (9 rows) -- collation matches the partitioning collation, pruning works create table coll_pruning (a text collate "C") partition by list (a); create table coll_pruning_a partition of coll_pruning for values in ('a'); create table coll_pruning_b partition of coll_pruning for values in ('b'); create table coll_pruning_def partition of coll_pruning default; explain (costs off) select * from coll_pruning where a collate "C" = 'a' collate "C"; QUERY PLAN ----------------------------------------- Seq Scan on coll_pruning_a coll_pruning Filter: (a = 'a'::text COLLATE "C") (2 rows) -- collation doesn't match the partitioning collation, no pruning occurs explain (costs off) select * from coll_pruning where a collate "POSIX" = 'a' collate "POSIX"; QUERY PLAN --------------------------------------------------------- Append -> Seq Scan on coll_pruning_a coll_pruning_1 Filter: ((a)::text = 'a'::text COLLATE "POSIX") -> Seq Scan on coll_pruning_b coll_pruning_2 Filter: ((a)::text = 'a'::text COLLATE "POSIX") -> Seq Scan on coll_pruning_def coll_pruning_3 Filter: ((a)::text = 'a'::text COLLATE "POSIX") (7 rows) create table rlp (a int, b varchar) partition by range (a); create table rlp_default partition of rlp default partition by list (a); create table rlp_default_default partition of rlp_default default; create table rlp_default_10 partition of rlp_default for values in (10); create table rlp_default_30 partition of rlp_default for values in (30); create table rlp_default_null partition of rlp_default for values in (null); create table rlp1 partition of rlp for values from (minvalue) to (1); create table rlp2 partition of rlp for values from (1) to (10); create table rlp3 (b varchar, a int) partition by list (b varchar_ops); create table rlp3_default partition of rlp3 default; create table rlp3abcd partition of rlp3 for values in ('ab', 'cd'); create table rlp3efgh partition of rlp3 for values in ('ef', 'gh'); create table rlp3nullxy partition of rlp3 for values in (null, 'xy'); alter table rlp attach partition rlp3 for values from (15) to (20); create table rlp4 partition of rlp for values from (20) to (30) partition by range (a); create table rlp4_default partition of rlp4 default; create table rlp4_1 partition of rlp4 for values from (20) to (25); create table rlp4_2 partition of rlp4 for values from (25) to (29); create table rlp5 partition of rlp for values from (31) to (maxvalue) partition by range (a); create table rlp5_default partition of rlp5 default; create table rlp5_1 partition of rlp5 for values from (31) to (40); explain (costs off) select * from rlp where a < 1; QUERY PLAN ---------------------- Seq Scan on rlp1 rlp Filter: (a < 1) (2 rows) explain (costs off) select * from rlp where 1 > a; /* commuted */ QUERY PLAN ---------------------- Seq Scan on rlp1 rlp Filter: (1 > a) (2 rows) explain (costs off) select * from rlp where a <= 1; QUERY PLAN ------------------------------ Append -> Seq Scan on rlp1 rlp_1 Filter: (a <= 1) -> Seq Scan on rlp2 rlp_2 Filter: (a <= 1) (5 rows) explain (costs off) select * from rlp where a = 1; QUERY PLAN ---------------------- Seq Scan on rlp2 rlp Filter: (a = 1) (2 rows) explain (costs off) select * from rlp where a = 1::bigint; /* same as above */ QUERY PLAN ----------------------------- Seq Scan on rlp2 rlp Filter: (a = '1'::bigint) (2 rows) explain (costs off) select * from rlp where a = 1::numeric; /* no pruning */ QUERY PLAN ----------------------------------------------- Append -> Seq Scan on rlp1 rlp_1 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp2 rlp_2 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp3abcd rlp_3 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp3efgh rlp_4 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp3nullxy rlp_5 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp3_default rlp_6 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp4_1 rlp_7 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp4_2 rlp_8 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp4_default rlp_9 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp5_1 rlp_10 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp5_default rlp_11 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp_default_10 rlp_12 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp_default_30 rlp_13 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp_default_null rlp_14 Filter: ((a)::numeric = '1'::numeric) -> Seq Scan on rlp_default_default rlp_15 Filter: ((a)::numeric = '1'::numeric) (31 rows) explain (costs off) select * from rlp where a <= 10; QUERY PLAN --------------------------------------------- Append -> Seq Scan on rlp1 rlp_1 Filter: (a <= 10) -> Seq Scan on rlp2 rlp_2 Filter: (a <= 10) -> Seq Scan on rlp_default_10 rlp_3 Filter: (a <= 10) -> Seq Scan on rlp_default_default rlp_4 Filter: (a <= 10) (9 rows) explain (costs off) select * from rlp where a > 10; QUERY PLAN ---------------------------------------------- Append -> Seq Scan on rlp3abcd rlp_1 Filter: (a > 10) -> Seq Scan on rlp3efgh rlp_2 Filter: (a > 10) -> Seq Scan on rlp3nullxy rlp_3 Filter: (a > 10) -> Seq Scan on rlp3_default rlp_4 Filter: (a > 10) -> Seq Scan on rlp4_1 rlp_5 Filter: (a > 10) -> Seq Scan on rlp4_2 rlp_6 Filter: (a > 10) -> Seq Scan on rlp4_default rlp_7 Filter: (a > 10) -> Seq Scan on rlp5_1 rlp_8 Filter: (a > 10) -> Seq Scan on rlp5_default rlp_9 Filter: (a > 10) -> Seq Scan on rlp_default_30 rlp_10 Filter: (a > 10) -> Seq Scan on rlp_default_default rlp_11 Filter: (a > 10) (23 rows) explain (costs off) select * from rlp where a < 15; QUERY PLAN --------------------------------------------- Append -> Seq Scan on rlp1 rlp_1 Filter: (a < 15) -> Seq Scan on rlp2 rlp_2 Filter: (a < 15) -> Seq Scan on rlp_default_10 rlp_3 Filter: (a < 15) -> Seq Scan on rlp_default_default rlp_4 Filter: (a < 15) (9 rows) explain (costs off) select * from rlp where a <= 15; QUERY PLAN --------------------------------------------- Append -> Seq Scan on rlp1 rlp_1 Filter: (a <= 15) -> Seq Scan on rlp2 rlp_2 Filter: (a <= 15) -> Seq Scan on rlp3abcd rlp_3 Filter: (a <= 15) -> Seq Scan on rlp3efgh rlp_4 Filter: (a <= 15) -> Seq Scan on rlp3nullxy rlp_5 Filter: (a <= 15) -> Seq Scan on rlp3_default rlp_6 Filter: (a <= 15) -> Seq Scan on rlp_default_10 rlp_7 Filter: (a <= 15) -> Seq Scan on rlp_default_default rlp_8 Filter: (a <= 15) (17 rows) explain (costs off) select * from rlp where a > 15 and b = 'ab'; QUERY PLAN --------------------------------------------------------- Append -> Seq Scan on rlp3abcd rlp_1 Filter: ((a > 15) AND ((b)::text = 'ab'::text)) -> Seq Scan on rlp4_1 rlp_2 Filter: ((a > 15) AND ((b)::text = 'ab'::text)) -> Seq Scan on rlp4_2 rlp_3 Filter: ((a > 15) AND ((b)::text = 'ab'::text)) -> Seq Scan on rlp4_default rlp_4 Filter: ((a > 15) AND ((b)::text = 'ab'::text)) -> Seq Scan on rlp5_1 rlp_5 Filter: ((a > 15) AND ((b)::text = 'ab'::text)) -> Seq Scan on rlp5_default rlp_6 Filter: ((a > 15) AND ((b)::text = 'ab'::text)) -> Seq Scan on rlp_default_30 rlp_7 Filter: ((a > 15) AND ((b)::text = 'ab'::text)) -> Seq Scan on rlp_default_default rlp_8 Filter: ((a > 15) AND ((b)::text = 'ab'::text)) (17 rows) explain (costs off) select * from rlp where a = 16; QUERY PLAN -------------------------------------- Append -> Seq Scan on rlp3abcd rlp_1 Filter: (a = 16) -> Seq Scan on rlp3efgh rlp_2 Filter: (a = 16) -> Seq Scan on rlp3nullxy rlp_3 Filter: (a = 16) -> Seq Scan on rlp3_default rlp_4 Filter: (a = 16) (9 rows) explain (costs off) select * from rlp where a = 16 and b in ('not', 'in', 'here'); QUERY PLAN ---------------------------------------------------------------------- Seq Scan on rlp3_default rlp Filter: ((a = 16) AND ((b)::text = ANY ('{not,in,here}'::text[]))) (2 rows) explain (costs off) select * from rlp where a = 16 and b < 'ab'; QUERY PLAN --------------------------------------------------- Seq Scan on rlp3_default rlp Filter: (((b)::text < 'ab'::text) AND (a = 16)) (2 rows) explain (costs off) select * from rlp where a = 16 and b <= 'ab'; QUERY PLAN ---------------------------------------------------------- Append -> Seq Scan on rlp3abcd rlp_1 Filter: (((b)::text <= 'ab'::text) AND (a = 16)) -> Seq Scan on rlp3_default rlp_2 Filter: (((b)::text <= 'ab'::text) AND (a = 16)) (5 rows) explain (costs off) select * from rlp where a = 16 and b is null; QUERY PLAN -------------------------------------- Seq Scan on rlp3nullxy rlp Filter: ((b IS NULL) AND (a = 16)) (2 rows) explain (costs off) select * from rlp where a = 16 and b is not null; QUERY PLAN ------------------------------------------------ Append -> Seq Scan on rlp3abcd rlp_1 Filter: ((b IS NOT NULL) AND (a = 16)) -> Seq Scan on rlp3efgh rlp_2 Filter: ((b IS NOT NULL) AND (a = 16)) -> Seq Scan on rlp3nullxy rlp_3 Filter: ((b IS NOT NULL) AND (a = 16)) -> Seq Scan on rlp3_default rlp_4 Filter: ((b IS NOT NULL) AND (a = 16)) (9 rows) explain (costs off) select * from rlp where a is null; QUERY PLAN ---------------------------------- Seq Scan on rlp_default_null rlp Filter: (a IS NULL) (2 rows) explain (costs off) select * from rlp where a is not null; QUERY PLAN ---------------------------------------------- Append -> Seq Scan on rlp1 rlp_1 Filter: (a IS NOT NULL) -> Seq Scan on rlp2 rlp_2 Filter: (a IS NOT NULL) -> Seq Scan on rlp3abcd rlp_3 Filter: (a IS NOT NULL) -> Seq Scan on rlp3efgh rlp_4 Filter: (a IS NOT NULL) -> Seq Scan on rlp3nullxy rlp_5 Filter: (a IS NOT NULL) -> Seq Scan on rlp3_default rlp_6 Filter: (a IS NOT NULL) -> Seq Scan on rlp4_1 rlp_7 Filter: (a IS NOT NULL) -> Seq Scan on rlp4_2 rlp_8 Filter: (a IS NOT NULL) -> Seq Scan on rlp4_default rlp_9 Filter: (a IS NOT NULL) -> Seq Scan on rlp5_1 rlp_10 Filter: (a IS NOT NULL) -> Seq Scan on rlp5_default rlp_11 Filter: (a IS NOT NULL) -> Seq Scan on rlp_default_10 rlp_12 Filter: (a IS NOT NULL) -> Seq Scan on rlp_default_30 rlp_13 Filter: (a IS NOT NULL) -> Seq Scan on rlp_default_default rlp_14 Filter: (a IS NOT NULL) (29 rows) explain (costs off) select * from rlp where a > 30; QUERY PLAN --------------------------------------------- Append -> Seq Scan on rlp5_1 rlp_1 Filter: (a > 30) -> Seq Scan on rlp5_default rlp_2 Filter: (a > 30) -> Seq Scan on rlp_default_default rlp_3 Filter: (a > 30) (7 rows) explain (costs off) select * from rlp where a = 30; /* only default is scanned */ QUERY PLAN -------------------------------- Seq Scan on rlp_default_30 rlp Filter: (a = 30) (2 rows) explain (costs off) select * from rlp where a <= 31; QUERY PLAN ---------------------------------------------- Append -> Seq Scan on rlp1 rlp_1 Filter: (a <= 31) -> Seq Scan on rlp2 rlp_2 Filter: (a <= 31) -> Seq Scan on rlp3abcd rlp_3 Filter: (a <= 31) -> Seq Scan on rlp3efgh rlp_4 Filter: (a <= 31) -> Seq Scan on rlp3nullxy rlp_5 Filter: (a <= 31) -> Seq Scan on rlp3_default rlp_6 Filter: (a <= 31) -> Seq Scan on rlp4_1 rlp_7 Filter: (a <= 31) -> Seq Scan on rlp4_2 rlp_8 Filter: (a <= 31) -> Seq Scan on rlp4_default rlp_9 Filter: (a <= 31) -> Seq Scan on rlp5_1 rlp_10 Filter: (a <= 31) -> Seq Scan on rlp_default_10 rlp_11 Filter: (a <= 31) -> Seq Scan on rlp_default_30 rlp_12 Filter: (a <= 31) -> Seq Scan on rlp_default_default rlp_13 Filter: (a <= 31) (27 rows) explain (costs off) select * from rlp where a = 1 or a = 7; QUERY PLAN -------------------------------- Seq Scan on rlp2 rlp Filter: ((a = 1) OR (a = 7)) (2 rows) explain (costs off) select * from rlp where a = 1 or b = 'ab'; QUERY PLAN ------------------------------------------------------- Append -> Seq Scan on rlp1 rlp_1 Filter: ((a = 1) OR ((b)::text = 'ab'::text)) -> Seq Scan on rlp2 rlp_2 Filter: ((a = 1) OR ((b)::text = 'ab'::text)) -> Seq Scan on rlp3abcd rlp_3 Filter: ((a = 1) OR ((b)::text = 'ab'::text)) -> Seq Scan on rlp4_1 rlp_4 Filter: ((a = 1) OR ((b)::text = 'ab'::text)) -> Seq Scan on rlp4_2 rlp_5 Filter: ((a = 1) OR ((b)::text = 'ab'::text)) -> Seq Scan on rlp4_default rlp_6 Filter: ((a = 1) OR ((b)::text = 'ab'::text)) -> Seq Scan on rlp5_1 rlp_7 Filter: ((a = 1) OR ((b)::text = 'ab'::text)) -> Seq Scan on rlp5_default rlp_8 Filter: ((a = 1) OR ((b)::text = 'ab'::text)) -> Seq Scan on rlp_default_10 rlp_9 Filter: ((a = 1) OR ((b)::text = 'ab'::text)) -> Seq Scan on rlp_default_30 rlp_10 Filter: ((a = 1) OR ((b)::text = 'ab'::text)) -> Seq Scan on rlp_default_null rlp_11 Filter: ((a = 1) OR ((b)::text = 'ab'::text)) -> Seq Scan on rlp_default_default rlp_12 Filter: ((a = 1) OR ((b)::text = 'ab'::text)) (25 rows) explain (costs off) select * from rlp where a > 20 and a < 27; QUERY PLAN ----------------------------------------- Append -> Seq Scan on rlp4_1 rlp_1 Filter: ((a > 20) AND (a < 27)) -> Seq Scan on rlp4_2 rlp_2 Filter: ((a > 20) AND (a < 27)) (5 rows) explain (costs off) select * from rlp where a = 29; QUERY PLAN ------------------------------ Seq Scan on rlp4_default rlp Filter: (a = 29) (2 rows) explain (costs off) select * from rlp where a >= 29; QUERY PLAN --------------------------------------------- Append -> Seq Scan on rlp4_default rlp_1 Filter: (a >= 29) -> Seq Scan on rlp5_1 rlp_2 Filter: (a >= 29) -> Seq Scan on rlp5_default rlp_3 Filter: (a >= 29) -> Seq Scan on rlp_default_30 rlp_4 Filter: (a >= 29) -> Seq Scan on rlp_default_default rlp_5 Filter: (a >= 29) (11 rows) explain (costs off) select * from rlp where a < 1 or (a > 20 and a < 25); QUERY PLAN ------------------------------------------------------ Append -> Seq Scan on rlp1 rlp_1 Filter: ((a < 1) OR ((a > 20) AND (a < 25))) -> Seq Scan on rlp4_1 rlp_2 Filter: ((a < 1) OR ((a > 20) AND (a < 25))) (5 rows) -- where clause contradicts sub-partition's constraint explain (costs off) select * from rlp where a = 20 or a = 40; QUERY PLAN ---------------------------------------- Append -> Seq Scan on rlp4_1 rlp_1 Filter: ((a = 20) OR (a = 40)) -> Seq Scan on rlp5_default rlp_2 Filter: ((a = 20) OR (a = 40)) (5 rows) explain (costs off) select * from rlp3 where a = 20; /* empty */ QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) -- redundant clauses are eliminated explain (costs off) select * from rlp where a > 1 and a = 10; /* only default */ QUERY PLAN ---------------------------------- Seq Scan on rlp_default_10 rlp Filter: ((a > 1) AND (a = 10)) (2 rows) explain (costs off) select * from rlp where a > 1 and a >=15; /* rlp3 onwards, including default */ QUERY PLAN ---------------------------------------------- Append -> Seq Scan on rlp3abcd rlp_1 Filter: ((a > 1) AND (a >= 15)) -> Seq Scan on rlp3efgh rlp_2 Filter: ((a > 1) AND (a >= 15)) -> Seq Scan on rlp3nullxy rlp_3 Filter: ((a > 1) AND (a >= 15)) -> Seq Scan on rlp3_default rlp_4 Filter: ((a > 1) AND (a >= 15)) -> Seq Scan on rlp4_1 rlp_5 Filter: ((a > 1) AND (a >= 15)) -> Seq Scan on rlp4_2 rlp_6 Filter: ((a > 1) AND (a >= 15)) -> Seq Scan on rlp4_default rlp_7 Filter: ((a > 1) AND (a >= 15)) -> Seq Scan on rlp5_1 rlp_8 Filter: ((a > 1) AND (a >= 15)) -> Seq Scan on rlp5_default rlp_9 Filter: ((a > 1) AND (a >= 15)) -> Seq Scan on rlp_default_30 rlp_10 Filter: ((a > 1) AND (a >= 15)) -> Seq Scan on rlp_default_default rlp_11 Filter: ((a > 1) AND (a >= 15)) (23 rows) explain (costs off) select * from rlp where a = 1 and a = 3; /* empty */ QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = 15); QUERY PLAN ------------------------------------------------------------------- Append -> Seq Scan on rlp2 rlp_1 Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) -> Seq Scan on rlp3abcd rlp_2 Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) -> Seq Scan on rlp3efgh rlp_3 Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) -> Seq Scan on rlp3nullxy rlp_4 Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) -> Seq Scan on rlp3_default rlp_5 Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) (11 rows) -- multi-column keys create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); create table mc3p_default partition of mc3p default; create table mc3p0 partition of mc3p for values from (minvalue, minvalue, minvalue) to (1, 1, 1); create table mc3p1 partition of mc3p for values from (1, 1, 1) to (10, 5, 10); create table mc3p2 partition of mc3p for values from (10, 5, 10) to (10, 10, 10); create table mc3p3 partition of mc3p for values from (10, 10, 10) to (10, 10, 20); create table mc3p4 partition of mc3p for values from (10, 10, 20) to (10, maxvalue, maxvalue); create table mc3p5 partition of mc3p for values from (11, 1, 1) to (20, 10, 10); create table mc3p6 partition of mc3p for values from (20, 10, 10) to (20, 20, 20); create table mc3p7 partition of mc3p for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue); explain (costs off) select * from mc3p where a = 1; QUERY PLAN --------------------------------------- Append -> Seq Scan on mc3p0 mc3p_1 Filter: (a = 1) -> Seq Scan on mc3p1 mc3p_2 Filter: (a = 1) -> Seq Scan on mc3p_default mc3p_3 Filter: (a = 1) (7 rows) explain (costs off) select * from mc3p where a = 1 and abs(b) < 1; QUERY PLAN -------------------------------------------- Append -> Seq Scan on mc3p0 mc3p_1 Filter: ((a = 1) AND (abs(b) < 1)) -> Seq Scan on mc3p_default mc3p_2 Filter: ((a = 1) AND (abs(b) < 1)) (5 rows) explain (costs off) select * from mc3p where a = 1 and abs(b) = 1; QUERY PLAN -------------------------------------------- Append -> Seq Scan on mc3p0 mc3p_1 Filter: ((a = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p1 mc3p_2 Filter: ((a = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p_default mc3p_3 Filter: ((a = 1) AND (abs(b) = 1)) (7 rows) explain (costs off) select * from mc3p where a = 1 and abs(b) = 1 and c < 8; QUERY PLAN -------------------------------------------------------- Append -> Seq Scan on mc3p0 mc3p_1 Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p1 mc3p_2 Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1)) (5 rows) explain (costs off) select * from mc3p where a = 10 and abs(b) between 5 and 35; QUERY PLAN ----------------------------------------------------------------- Append -> Seq Scan on mc3p1 mc3p_1 Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) -> Seq Scan on mc3p2 mc3p_2 Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) -> Seq Scan on mc3p3 mc3p_3 Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) -> Seq Scan on mc3p4 mc3p_4 Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) -> Seq Scan on mc3p_default mc3p_5 Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) (11 rows) explain (costs off) select * from mc3p where a > 10; QUERY PLAN --------------------------------------- Append -> Seq Scan on mc3p5 mc3p_1 Filter: (a > 10) -> Seq Scan on mc3p6 mc3p_2 Filter: (a > 10) -> Seq Scan on mc3p7 mc3p_3 Filter: (a > 10) -> Seq Scan on mc3p_default mc3p_4 Filter: (a > 10) (9 rows) explain (costs off) select * from mc3p where a >= 10; QUERY PLAN --------------------------------------- Append -> Seq Scan on mc3p1 mc3p_1 Filter: (a >= 10) -> Seq Scan on mc3p2 mc3p_2 Filter: (a >= 10) -> Seq Scan on mc3p3 mc3p_3 Filter: (a >= 10) -> Seq Scan on mc3p4 mc3p_4 Filter: (a >= 10) -> Seq Scan on mc3p5 mc3p_5 Filter: (a >= 10) -> Seq Scan on mc3p6 mc3p_6 Filter: (a >= 10) -> Seq Scan on mc3p7 mc3p_7 Filter: (a >= 10) -> Seq Scan on mc3p_default mc3p_8 Filter: (a >= 10) (17 rows) explain (costs off) select * from mc3p where a < 10; QUERY PLAN --------------------------------------- Append -> Seq Scan on mc3p0 mc3p_1 Filter: (a < 10) -> Seq Scan on mc3p1 mc3p_2 Filter: (a < 10) -> Seq Scan on mc3p_default mc3p_3 Filter: (a < 10) (7 rows) explain (costs off) select * from mc3p where a <= 10 and abs(b) < 10; QUERY PLAN ----------------------------------------------- Append -> Seq Scan on mc3p0 mc3p_1 Filter: ((a <= 10) AND (abs(b) < 10)) -> Seq Scan on mc3p1 mc3p_2 Filter: ((a <= 10) AND (abs(b) < 10)) -> Seq Scan on mc3p2 mc3p_3 Filter: ((a <= 10) AND (abs(b) < 10)) -> Seq Scan on mc3p_default mc3p_4 Filter: ((a <= 10) AND (abs(b) < 10)) (9 rows) explain (costs off) select * from mc3p where a = 11 and abs(b) = 0; QUERY PLAN --------------------------------------- Seq Scan on mc3p_default mc3p Filter: ((a = 11) AND (abs(b) = 0)) (2 rows) explain (costs off) select * from mc3p where a = 20 and abs(b) = 10 and c = 100; QUERY PLAN ------------------------------------------------------ Seq Scan on mc3p6 mc3p Filter: ((a = 20) AND (c = 100) AND (abs(b) = 10)) (2 rows) explain (costs off) select * from mc3p where a > 20; QUERY PLAN --------------------------------------- Append -> Seq Scan on mc3p7 mc3p_1 Filter: (a > 20) -> Seq Scan on mc3p_default mc3p_2 Filter: (a > 20) (5 rows) explain (costs off) select * from mc3p where a >= 20; QUERY PLAN --------------------------------------- Append -> Seq Scan on mc3p5 mc3p_1 Filter: (a >= 20) -> Seq Scan on mc3p6 mc3p_2 Filter: (a >= 20) -> Seq Scan on mc3p7 mc3p_3 Filter: (a >= 20) -> Seq Scan on mc3p_default mc3p_4 Filter: (a >= 20) (9 rows) explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20); QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------- Append -> Seq Scan on mc3p1 mc3p_1 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20))) -> Seq Scan on mc3p2 mc3p_2 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20))) -> Seq Scan on mc3p5 mc3p_3 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20))) -> Seq Scan on mc3p_default mc3p_4 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20))) (9 rows) explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20) or a < 1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------- Append -> Seq Scan on mc3p0 mc3p_1 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) -> Seq Scan on mc3p1 mc3p_2 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) -> Seq Scan on mc3p2 mc3p_3 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) -> Seq Scan on mc3p5 mc3p_4 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) -> Seq Scan on mc3p_default mc3p_5 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) (11 rows) explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20) or a < 1 or a = 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------- Append -> Seq Scan on mc3p0 mc3p_1 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) -> Seq Scan on mc3p1 mc3p_2 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) -> Seq Scan on mc3p2 mc3p_3 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) -> Seq Scan on mc3p5 mc3p_4 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) -> Seq Scan on mc3p_default mc3p_5 Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) (11 rows) explain (costs off) select * from mc3p where a = 1 or abs(b) = 1 or c = 1; QUERY PLAN ------------------------------------------------------ Append -> Seq Scan on mc3p0 mc3p_1 Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) -> Seq Scan on mc3p1 mc3p_2 Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) -> Seq Scan on mc3p2 mc3p_3 Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) -> Seq Scan on mc3p3 mc3p_4 Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) -> Seq Scan on mc3p4 mc3p_5 Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) -> Seq Scan on mc3p5 mc3p_6 Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) -> Seq Scan on mc3p6 mc3p_7 Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) -> Seq Scan on mc3p7 mc3p_8 Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) -> Seq Scan on mc3p_default mc3p_9 Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) (19 rows) explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 and abs(b) = 10); QUERY PLAN ------------------------------------------------------------------------------ Append -> Seq Scan on mc3p0 mc3p_1 Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) -> Seq Scan on mc3p1 mc3p_2 Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) -> Seq Scan on mc3p2 mc3p_3 Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) -> Seq Scan on mc3p3 mc3p_4 Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) -> Seq Scan on mc3p4 mc3p_5 Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) -> Seq Scan on mc3p_default mc3p_6 Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) (13 rows) explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 and abs(b) = 9); QUERY PLAN ----------------------------------------------------------------------------- Append -> Seq Scan on mc3p0 mc3p_1 Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9))) -> Seq Scan on mc3p1 mc3p_2 Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9))) -> Seq Scan on mc3p2 mc3p_3 Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9))) -> Seq Scan on mc3p_default mc3p_4 Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9))) (9 rows) -- a simpler multi-column keys case create table mc2p (a int, b int) partition by range (a, b); create table mc2p_default partition of mc2p default; create table mc2p0 partition of mc2p for values from (minvalue, minvalue) to (1, minvalue); create table mc2p1 partition of mc2p for values from (1, minvalue) to (1, 1); create table mc2p2 partition of mc2p for values from (1, 1) to (2, minvalue); create table mc2p3 partition of mc2p for values from (2, minvalue) to (2, 1); create table mc2p4 partition of mc2p for values from (2, 1) to (2, maxvalue); create table mc2p5 partition of mc2p for values from (2, maxvalue) to (maxvalue, maxvalue); explain (costs off) select * from mc2p where a < 2; QUERY PLAN --------------------------------------- Append -> Seq Scan on mc2p0 mc2p_1 Filter: (a < 2) -> Seq Scan on mc2p1 mc2p_2 Filter: (a < 2) -> Seq Scan on mc2p2 mc2p_3 Filter: (a < 2) -> Seq Scan on mc2p_default mc2p_4 Filter: (a < 2) (9 rows) explain (costs off) select * from mc2p where a = 2 and b < 1; QUERY PLAN --------------------------------- Seq Scan on mc2p3 mc2p Filter: ((b < 1) AND (a = 2)) (2 rows) explain (costs off) select * from mc2p where a > 1; QUERY PLAN --------------------------------------- Append -> Seq Scan on mc2p2 mc2p_1 Filter: (a > 1) -> Seq Scan on mc2p3 mc2p_2 Filter: (a > 1) -> Seq Scan on mc2p4 mc2p_3 Filter: (a > 1) -> Seq Scan on mc2p5 mc2p_4 Filter: (a > 1) -> Seq Scan on mc2p_default mc2p_5 Filter: (a > 1) (11 rows) explain (costs off) select * from mc2p where a = 1 and b > 1; QUERY PLAN --------------------------------- Seq Scan on mc2p2 mc2p Filter: ((b > 1) AND (a = 1)) (2 rows) -- all partitions but the default one should be pruned explain (costs off) select * from mc2p where a = 1 and b is null; QUERY PLAN ------------------------------------- Seq Scan on mc2p_default mc2p Filter: ((b IS NULL) AND (a = 1)) (2 rows) explain (costs off) select * from mc2p where a is null and b is null; QUERY PLAN ----------------------------------------- Seq Scan on mc2p_default mc2p Filter: ((a IS NULL) AND (b IS NULL)) (2 rows) explain (costs off) select * from mc2p where a is null and b = 1; QUERY PLAN ------------------------------------- Seq Scan on mc2p_default mc2p Filter: ((a IS NULL) AND (b = 1)) (2 rows) explain (costs off) select * from mc2p where a is null; QUERY PLAN ------------------------------- Seq Scan on mc2p_default mc2p Filter: (a IS NULL) (2 rows) explain (costs off) select * from mc2p where b is null; QUERY PLAN ------------------------------- Seq Scan on mc2p_default mc2p Filter: (b IS NULL) (2 rows) -- boolean partitioning create table boolpart (a bool) partition by list (a); create table boolpart_default partition of boolpart default; create table boolpart_t partition of boolpart for values in ('true'); create table boolpart_f partition of boolpart for values in ('false'); insert into boolpart values (true), (false), (null); explain (costs off) select * from boolpart where a in (true, false); QUERY PLAN ------------------------------------------------ Append -> Seq Scan on boolpart_f boolpart_1 Filter: (a = ANY ('{t,f}'::boolean[])) -> Seq Scan on boolpart_t boolpart_2 Filter: (a = ANY ('{t,f}'::boolean[])) (5 rows) explain (costs off) select * from boolpart where a = false; QUERY PLAN --------------------------------- Seq Scan on boolpart_f boolpart Filter: (NOT a) (2 rows) explain (costs off) select * from boolpart where not a = false; QUERY PLAN --------------------------------- Seq Scan on boolpart_t boolpart Filter: a (2 rows) explain (costs off) select * from boolpart where a is true or a is not true; QUERY PLAN -------------------------------------------------- Append -> Seq Scan on boolpart_f boolpart_1 Filter: ((a IS TRUE) OR (a IS NOT TRUE)) -> Seq Scan on boolpart_t boolpart_2 Filter: ((a IS TRUE) OR (a IS NOT TRUE)) -> Seq Scan on boolpart_default boolpart_3 Filter: ((a IS TRUE) OR (a IS NOT TRUE)) (7 rows) explain (costs off) select * from boolpart where a is not true; QUERY PLAN ----------------------------------------------- Append -> Seq Scan on boolpart_f boolpart_1 Filter: (a IS NOT TRUE) -> Seq Scan on boolpart_default boolpart_2 Filter: (a IS NOT TRUE) (5 rows) explain (costs off) select * from boolpart where a is not true and a is not false; QUERY PLAN -------------------------------------------------- Seq Scan on boolpart_default boolpart Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) (2 rows) explain (costs off) select * from boolpart where a is unknown; QUERY PLAN --------------------------------------- Seq Scan on boolpart_default boolpart Filter: (a IS UNKNOWN) (2 rows) explain (costs off) select * from boolpart where a is not unknown; QUERY PLAN ----------------------------------------------- Append -> Seq Scan on boolpart_f boolpart_1 Filter: (a IS NOT UNKNOWN) -> Seq Scan on boolpart_t boolpart_2 Filter: (a IS NOT UNKNOWN) -> Seq Scan on boolpart_default boolpart_3 Filter: (a IS NOT UNKNOWN) (7 rows) select * from boolpart where a in (true, false); a --- f t (2 rows) select * from boolpart where a = false; a --- f (1 row) select * from boolpart where not a = false; a --- t (1 row) select * from boolpart where a is true or a is not true; a --- f t (3 rows) select * from boolpart where a is not true; a --- f (2 rows) select * from boolpart where a is not true and a is not false; a --- (1 row) select * from boolpart where a is unknown; a --- (1 row) select * from boolpart where a is not unknown; a --- f t (2 rows) -- try some other permutations with a NULL partition instead of a DEFAULT delete from boolpart where a is null; create table boolpart_null partition of boolpart for values in (null); insert into boolpart values(null); explain (costs off) select * from boolpart where a is not true; QUERY PLAN -------------------------------------------- Append -> Seq Scan on boolpart_f boolpart_1 Filter: (a IS NOT TRUE) -> Seq Scan on boolpart_null boolpart_2 Filter: (a IS NOT TRUE) (5 rows) explain (costs off) select * from boolpart where a is not true and a is not false; QUERY PLAN -------------------------------------------------- Seq Scan on boolpart_null boolpart Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) (2 rows) explain (costs off) select * from boolpart where a is not false; QUERY PLAN -------------------------------------------- Append -> Seq Scan on boolpart_t boolpart_1 Filter: (a IS NOT FALSE) -> Seq Scan on boolpart_null boolpart_2 Filter: (a IS NOT FALSE) (5 rows) explain (costs off) select * from boolpart where a is not unknown; QUERY PLAN ----------------------------------------------- Append -> Seq Scan on boolpart_f boolpart_1 Filter: (a IS NOT UNKNOWN) -> Seq Scan on boolpart_t boolpart_2 Filter: (a IS NOT UNKNOWN) -> Seq Scan on boolpart_default boolpart_3 Filter: (a IS NOT UNKNOWN) (7 rows) select * from boolpart where a is not true; a --- f (2 rows) select * from boolpart where a is not true and a is not false; a --- (1 row) select * from boolpart where a is not false; a --- t (2 rows) select * from boolpart where a is not unknown; a --- f t (2 rows) -- check that all partitions are pruned when faced with conflicting clauses explain (costs off) select * from boolpart where a is not unknown and a is unknown; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) explain (costs off) select * from boolpart where a is false and a is unknown; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) explain (costs off) select * from boolpart where a is true and a is unknown; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) -- inverse boolean partitioning - a seemingly unlikely design, but we've got -- code for it, so we'd better test it. create table iboolpart (a bool) partition by list ((not a)); create table iboolpart_default partition of iboolpart default; create table iboolpart_f partition of iboolpart for values in ('true'); create table iboolpart_t partition of iboolpart for values in ('false'); insert into iboolpart values (true), (false), (null); explain (costs off) select * from iboolpart where a in (true, false); QUERY PLAN ------------------------------------------------- Append -> Seq Scan on iboolpart_t iboolpart_1 Filter: (a = ANY ('{t,f}'::boolean[])) -> Seq Scan on iboolpart_f iboolpart_2 Filter: (a = ANY ('{t,f}'::boolean[])) -> Seq Scan on iboolpart_default iboolpart_3 Filter: (a = ANY ('{t,f}'::boolean[])) (7 rows) explain (costs off) select * from iboolpart where a = false; QUERY PLAN ----------------------------------- Seq Scan on iboolpart_f iboolpart Filter: (NOT a) (2 rows) explain (costs off) select * from iboolpart where not a = false; QUERY PLAN ----------------------------------- Seq Scan on iboolpart_t iboolpart Filter: a (2 rows) explain (costs off) select * from iboolpart where a is true or a is not true; QUERY PLAN -------------------------------------------------- Append -> Seq Scan on iboolpart_t iboolpart_1 Filter: ((a IS TRUE) OR (a IS NOT TRUE)) -> Seq Scan on iboolpart_f iboolpart_2 Filter: ((a IS TRUE) OR (a IS NOT TRUE)) -> Seq Scan on iboolpart_default iboolpart_3 Filter: ((a IS TRUE) OR (a IS NOT TRUE)) (7 rows) explain (costs off) select * from iboolpart where a is not true; QUERY PLAN ------------------------------------------------- Append -> Seq Scan on iboolpart_t iboolpart_1 Filter: (a IS NOT TRUE) -> Seq Scan on iboolpart_f iboolpart_2 Filter: (a IS NOT TRUE) -> Seq Scan on iboolpart_default iboolpart_3 Filter: (a IS NOT TRUE) (7 rows) explain (costs off) select * from iboolpart where a is not true and a is not false; QUERY PLAN -------------------------------------------------------- Append -> Seq Scan on iboolpart_t iboolpart_1 Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) -> Seq Scan on iboolpart_f iboolpart_2 Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) -> Seq Scan on iboolpart_default iboolpart_3 Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) (7 rows) explain (costs off) select * from iboolpart where a is unknown; QUERY PLAN ------------------------------------------------- Append -> Seq Scan on iboolpart_t iboolpart_1 Filter: (a IS UNKNOWN) -> Seq Scan on iboolpart_f iboolpart_2 Filter: (a IS UNKNOWN) -> Seq Scan on iboolpart_default iboolpart_3 Filter: (a IS UNKNOWN) (7 rows) explain (costs off) select * from iboolpart where a is not unknown; QUERY PLAN ------------------------------------------------- Append -> Seq Scan on iboolpart_t iboolpart_1 Filter: (a IS NOT UNKNOWN) -> Seq Scan on iboolpart_f iboolpart_2 Filter: (a IS NOT UNKNOWN) -> Seq Scan on iboolpart_default iboolpart_3 Filter: (a IS NOT UNKNOWN) (7 rows) select * from iboolpart where a in (true, false); a --- t f (2 rows) select * from iboolpart where a = false; a --- f (1 row) select * from iboolpart where not a = false; a --- t (1 row) select * from iboolpart where a is true or a is not true; a --- t f (3 rows) select * from iboolpart where a is not true; a --- f (2 rows) select * from iboolpart where a is not true and a is not false; a --- (1 row) select * from iboolpart where a is unknown; a --- (1 row) select * from iboolpart where a is not unknown; a --- t f (2 rows) -- Try some other permutations with a NULL partition instead of a DEFAULT delete from iboolpart where a is null; create table iboolpart_null partition of iboolpart for values in (null); insert into iboolpart values(null); -- Pruning shouldn't take place for these. Just check the result is correct select * from iboolpart where a is not true; a --- f (2 rows) select * from iboolpart where a is not true and a is not false; a --- (1 row) select * from iboolpart where a is not false; a --- t (2 rows) create table boolrangep (a bool, b bool, c int) partition by range (a,b,c); create table boolrangep_tf partition of boolrangep for values from ('true', 'false', 0) to ('true', 'false', 100); create table boolrangep_ft partition of boolrangep for values from ('false', 'true', 0) to ('false', 'true', 100); create table boolrangep_ff1 partition of boolrangep for values from ('false', 'false', 0) to ('false', 'false', 50); create table boolrangep_ff2 partition of boolrangep for values from ('false', 'false', 50) to ('false', 'false', 100); create table 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 where not a and not b and c = 25; QUERY PLAN ---------------------------------------------- Seq Scan on boolrangep_ff1 boolrangep Filter: ((NOT a) AND (NOT b) AND (c = 25)) (2 rows) -- ensure we prune boolrangep_tf explain (costs off) select * from boolrangep where a is not true and not b and c = 25; QUERY PLAN ------------------------------------------------------------ Append -> Seq Scan on boolrangep_ff1 boolrangep_1 Filter: ((a IS NOT TRUE) AND (NOT b) AND (c = 25)) -> Seq Scan on boolrangep_ff2 boolrangep_2 Filter: ((a IS NOT TRUE) AND (NOT b) AND (c = 25)) -> Seq Scan on boolrangep_ft boolrangep_3 Filter: ((a IS NOT TRUE) AND (NOT b) AND (c = 25)) -> Seq Scan on boolrangep_null boolrangep_4 Filter: ((a IS NOT TRUE) AND (NOT b) AND (c = 25)) (9 rows) -- ensure we prune everything apart from boolrangep_tf and boolrangep_null explain (costs off) select * from boolrangep where a is not false and not b and c = 25; QUERY PLAN ------------------------------------------------------------- Append -> Seq Scan on boolrangep_tf boolrangep_1 Filter: ((a IS NOT FALSE) AND (NOT b) AND (c = 25)) -> Seq Scan on boolrangep_null boolrangep_2 Filter: ((a IS NOT FALSE) AND (NOT b) AND (c = 25)) (5 rows) -- test scalar-to-array operators create table coercepart (a varchar) partition by list (a); create table coercepart_ab partition of coercepart for values in ('ab'); create table coercepart_bc partition of coercepart for values in ('bc'); create table coercepart_cd partition of coercepart for values in ('cd'); explain (costs off) select * from coercepart where a in ('ab', to_char(125, '999')); QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------ Append -> Seq Scan on coercepart_ab coercepart_1 Filter: ((a)::text = ANY ((ARRAY['ab'::character varying, (to_char(125, '999'::text))::character varying])::text[])) -> Seq Scan on coercepart_bc coercepart_2 Filter: ((a)::text = ANY ((ARRAY['ab'::character varying, (to_char(125, '999'::text))::character varying])::text[])) -> Seq Scan on coercepart_cd coercepart_3 Filter: ((a)::text = ANY ((ARRAY['ab'::character varying, (to_char(125, '999'::text))::character varying])::text[])) (7 rows) explain (costs off) select * from coercepart where a ~ any ('{ab}'); QUERY PLAN ---------------------------------------------------- Append -> Seq Scan on coercepart_ab coercepart_1 Filter: ((a)::text ~ ANY ('{ab}'::text[])) -> Seq Scan on coercepart_bc coercepart_2 Filter: ((a)::text ~ ANY ('{ab}'::text[])) -> Seq Scan on coercepart_cd coercepart_3 Filter: ((a)::text ~ ANY ('{ab}'::text[])) (7 rows) explain (costs off) select * from coercepart where a !~ all ('{ab}'); QUERY PLAN ----------------------------------------------------- Append -> Seq Scan on coercepart_ab coercepart_1 Filter: ((a)::text !~ ALL ('{ab}'::text[])) -> Seq Scan on coercepart_bc coercepart_2 Filter: ((a)::text !~ ALL ('{ab}'::text[])) -> Seq Scan on coercepart_cd coercepart_3 Filter: ((a)::text !~ ALL ('{ab}'::text[])) (7 rows) explain (costs off) select * from coercepart where a ~ any ('{ab,bc}'); QUERY PLAN ------------------------------------------------------- Append -> Seq Scan on coercepart_ab coercepart_1 Filter: ((a)::text ~ ANY ('{ab,bc}'::text[])) -> Seq Scan on coercepart_bc coercepart_2 Filter: ((a)::text ~ ANY ('{ab,bc}'::text[])) -> Seq Scan on coercepart_cd coercepart_3 Filter: ((a)::text ~ ANY ('{ab,bc}'::text[])) (7 rows) explain (costs off) select * from coercepart where a !~ all ('{ab,bc}'); QUERY PLAN -------------------------------------------------------- Append -> Seq Scan on coercepart_ab coercepart_1 Filter: ((a)::text !~ ALL ('{ab,bc}'::text[])) -> Seq Scan on coercepart_bc coercepart_2 Filter: ((a)::text !~ ALL ('{ab,bc}'::text[])) -> Seq Scan on coercepart_cd coercepart_3 Filter: ((a)::text !~ ALL ('{ab,bc}'::text[])) (7 rows) explain (costs off) select * from coercepart where a = any ('{ab,bc}'); QUERY PLAN ------------------------------------------------------- Append -> Seq Scan on coercepart_ab coercepart_1 Filter: ((a)::text = ANY ('{ab,bc}'::text[])) -> Seq Scan on coercepart_bc coercepart_2 Filter: ((a)::text = ANY ('{ab,bc}'::text[])) (5 rows) explain (costs off) select * from coercepart where a = any ('{ab,null}'); QUERY PLAN --------------------------------------------------- Seq Scan on coercepart_ab coercepart Filter: ((a)::text = ANY ('{ab,NULL}'::text[])) (2 rows) explain (costs off) select * from coercepart where a = any (null::text[]); QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) explain (costs off) select * from coercepart where a = all ('{ab}'); QUERY PLAN ---------------------------------------------- Seq Scan on coercepart_ab coercepart Filter: ((a)::text = ALL ('{ab}'::text[])) (2 rows) explain (costs off) select * from coercepart where a = all ('{ab,bc}'); QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) explain (costs off) select * from coercepart where a = all ('{ab,null}'); QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) explain (costs off) select * from coercepart where a = all (null::text[]); QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) drop table coercepart; CREATE TABLE part (a INT, b INT) PARTITION BY LIST (a); CREATE TABLE part_p1 PARTITION OF part FOR VALUES IN (-2,-1,0,1,2); CREATE TABLE part_p2 PARTITION OF part DEFAULT PARTITION BY RANGE(a); CREATE TABLE part_p2_p1 PARTITION OF part_p2 DEFAULT; CREATE TABLE part_rev (b INT, c INT, a INT); ALTER TABLE part ATTACH PARTITION part_rev FOR VALUES IN (3); -- fail ERROR: table "part_rev" contains column "c" not found in parent "part" DETAIL: The new partition may contain only the columns present in parent. ALTER TABLE part_rev DROP COLUMN c; ALTER TABLE part ATTACH PARTITION part_rev FOR VALUES IN (3); -- now it's ok INSERT INTO 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 IS NULL ORDER BY 1, 2, 3; QUERY PLAN --------------------------------------------------------- Sort Sort Key: ((part.tableoid)::regclass), part.a, part.b -> Seq Scan on part_p2_p1 part Filter: (a IS NULL) (4 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM part p(x) ORDER BY x; QUERY PLAN ----------------------------------------------- Sort Output: p.x, p.b Sort Key: p.x -> Append -> Seq Scan on public.part_p1 p_1 Output: p_1.x, p_1.b -> Seq Scan on public.part_rev p_2 Output: p_2.x, p_2.b -> Seq Scan on public.part_p2_p1 p_3 Output: p_3.x, p_3.b (10 rows) -- -- 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) = 1 and t2.c = 1) s where t1.a = 1; QUERY PLAN ----------------------------------------------------------------------- Nested Loop -> Append -> Seq Scan on mc2p1 t1_1 Filter: (a = 1) -> Seq Scan on mc2p2 t1_2 Filter: (a = 1) -> Seq Scan on mc2p_default t1_3 Filter: (a = 1) -> Aggregate -> Append -> Seq Scan on mc3p0 t2_1 Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p1 t2_2 Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p2 t2_3 Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p3 t2_4 Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p4 t2_5 Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p5 t2_6 Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p6 t2_7 Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p7 t2_8 Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p_default t2_9 Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) (28 rows) -- 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) = 1 and t2.a = 1) s where t1.a = 1; QUERY PLAN ----------------------------------------------------------------------- Nested Loop -> Append -> Seq Scan on mc2p1 t1_1 Filter: (a = 1) -> Seq Scan on mc2p2 t1_2 Filter: (a = 1) -> Seq Scan on mc2p_default t1_3 Filter: (a = 1) -> Aggregate -> Append -> Seq Scan on mc3p0 t2_1 Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p1 t2_2 Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1)) -> Seq Scan on mc3p_default t2_3 Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1)) (16 rows) -- 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 = 1 and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1; QUERY PLAN -------------------------------------------------------------- Nested Loop -> Aggregate -> Seq Scan on mc3p1 t2 Filter: ((a = 1) AND (c = 1) AND (abs(b) = 1)) -> Append -> Seq Scan on mc2p1 t1_1 Filter: (a = 1) -> Seq Scan on mc2p2 t1_2 Filter: (a = 1) -> Seq Scan on mc2p_default t1_3 Filter: (a = 1) (11 rows) -- -- pruning with clauses containing <> operator -- -- doesn't prune range partitions create table rp (a int) partition by range (a); create table rp0 partition of rp for values from (minvalue) to (1); create table rp1 partition of rp for values from (1) to (2); create table rp2 partition of rp for values from (2) to (maxvalue); explain (costs off) select * from rp where a <> 1; QUERY PLAN ---------------------------- Append -> Seq Scan on rp0 rp_1 Filter: (a <> 1) -> Seq Scan on rp1 rp_2 Filter: (a <> 1) -> Seq Scan on rp2 rp_3 Filter: (a <> 1) (7 rows) explain (costs off) select * from rp where a <> 1 and a <> 2; QUERY PLAN ----------------------------------------- Append -> Seq Scan on rp0 rp_1 Filter: ((a <> 1) AND (a <> 2)) -> Seq Scan on rp1 rp_2 Filter: ((a <> 1) AND (a <> 2)) -> Seq Scan on rp2 rp_3 Filter: ((a <> 1) AND (a <> 2)) (7 rows) -- null partition should be eliminated due to strict <> clause. explain (costs off) select * from lp where a <> 'a'; QUERY PLAN ------------------------------------ Append -> Seq Scan on lp_ad lp_1 Filter: (a <> 'a'::bpchar) -> Seq Scan on lp_bc lp_2 Filter: (a <> 'a'::bpchar) -> Seq Scan on lp_ef lp_3 Filter: (a <> 'a'::bpchar) -> Seq Scan on lp_g lp_4 Filter: (a <> 'a'::bpchar) -> Seq Scan on lp_default lp_5 Filter: (a <> 'a'::bpchar) (11 rows) -- 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 is null; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) explain (costs off) select * from lp where (a <> 'a' and a <> 'd') or a is null; QUERY PLAN ------------------------------------------------------------------------------ Append -> Seq Scan on lp_bc lp_1 Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) -> Seq Scan on lp_ef lp_2 Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) -> Seq Scan on lp_g lp_3 Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) -> Seq Scan on lp_null lp_4 Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) -> Seq Scan on lp_default lp_5 Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) (11 rows) -- 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 = 15 and b <> 'ab' and b <> 'cd' and b <> 'xy' and b is not null; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------ Append -> Seq Scan on rlp3efgh rlp_1 Filter: ((b IS NOT NULL) AND ((b)::text <> 'ab'::text) AND ((b)::text <> 'cd'::text) AND ((b)::text <> 'xy'::text) AND (a = 15)) -> Seq Scan on rlp3_default rlp_2 Filter: ((b IS NOT NULL) AND ((b)::text <> 'ab'::text) AND ((b)::text <> 'cd'::text) AND ((b)::text <> 'xy'::text) AND (a = 15)) (5 rows) -- -- different collations for different keys with same expression -- create table coll_pruning_multi (a text) partition by range (substr(a, 1) collate "POSIX", substr(a, 1) collate "C"); create table coll_pruning_multi1 partition of coll_pruning_multi for values from ('a', 'a') to ('a', 'e'); create table coll_pruning_multi2 partition of coll_pruning_multi for values from ('a', 'e') to ('a', 'z'); create table coll_pruning_multi3 partition of coll_pruning_multi for values from ('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"; QUERY PLAN ------------------------------------------------------------ Append -> Seq Scan on coll_pruning_multi1 coll_pruning_multi_1 Filter: (substr(a, 1) = 'e'::text COLLATE "C") -> Seq Scan on coll_pruning_multi2 coll_pruning_multi_2 Filter: (substr(a, 1) = 'e'::text COLLATE "C") -> Seq Scan on coll_pruning_multi3 coll_pruning_multi_3 Filter: (substr(a, 1) = 'e'::text COLLATE "C") (7 rows) -- pruning, with a value provided for the leading key explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'a' collate "POSIX"; QUERY PLAN ------------------------------------------------------------ Append -> Seq Scan on coll_pruning_multi1 coll_pruning_multi_1 Filter: (substr(a, 1) = 'a'::text COLLATE "POSIX") -> Seq Scan on coll_pruning_multi2 coll_pruning_multi_2 Filter: (substr(a, 1) = 'a'::text COLLATE "POSIX") (5 rows) -- 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"; QUERY PLAN --------------------------------------------------------------------------------------------------- Seq Scan on coll_pruning_multi2 coll_pruning_multi Filter: ((substr(a, 1) = 'e'::text COLLATE "C") AND (substr(a, 1) = 'a'::text COLLATE "POSIX")) (2 rows) -- -- LIKE operators don't prune -- create table like_op_noprune (a text) partition by list (a); create table like_op_noprune1 partition of like_op_noprune for values in ('ABC'); create table like_op_noprune2 partition of like_op_noprune for values in ('BCD'); explain (costs off) select * from like_op_noprune where a like '%BC'; QUERY PLAN ------------------------------------------------------ Append -> Seq Scan on like_op_noprune1 like_op_noprune_1 Filter: (a ~~ '%BC'::text) -> Seq Scan on like_op_noprune2 like_op_noprune_2 Filter: (a ~~ '%BC'::text) (5 rows) -- -- tests wherein clause value requires a cross-type comparison function -- create table lparted_by_int2 (a smallint) partition by list (a); create table lparted_by_int2_1 partition of lparted_by_int2 for values in (1); create table lparted_by_int2_16384 partition of lparted_by_int2 for values in (16384); explain (costs off) select * from lparted_by_int2 where a = 100_000_000_000_000; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) create table rparted_by_int2 (a smallint) partition by range (a); create table rparted_by_int2_1 partition of rparted_by_int2 for values from (1) to (10); create table rparted_by_int2_16384 partition of rparted_by_int2 for values from (10) to (16384); -- all partitions pruned explain (costs off) select * from rparted_by_int2 where a > 100_000_000_000_000; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) create table rparted_by_int2_maxvalue partition of rparted_by_int2 for values from (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; QUERY PLAN ------------------------------------------------------ Seq Scan on rparted_by_int2_maxvalue rparted_by_int2 Filter: (a > '100000000000000'::bigint) (2 rows) drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, iboolpart, boolrangep, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2; -- -- 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. -- create table hp (a int, b text, c int) partition by hash (a part_test_int4_ops, b part_test_text_ops); create table hp0 partition of hp for values with (modulus 4, remainder 0); create table hp3 partition of hp for values with (modulus 4, remainder 3); create table hp1 partition of hp for values with (modulus 4, remainder 1); create table hp2 partition of hp for values with (modulus 4, remainder 2); insert into hp values (null, null, 0); insert into hp values (1, null, 1); insert into hp values (1, 'xxx', 2); insert into hp values (null, 'xxx', 3); insert into hp values (2, 'xxx', 4); insert into hp values (1, 'abcde', 5); select tableoid::regclass, * from hp order by c; tableoid | a | b | c ----------+---+-------+--- hp0 | | | 0 hp1 | 1 | | 1 hp0 | 1 | xxx | 2 hp2 | | xxx | 3 hp3 | 2 | xxx | 4 hp2 | 1 | abcde | 5 (6 rows) -- partial keys won't prune, nor would non-equality conditions explain (costs off) select * from hp where a = 1; QUERY PLAN ---------------------------- Append -> Seq Scan on hp0 hp_1 Filter: (a = 1) -> Seq Scan on hp1 hp_2 Filter: (a = 1) -> Seq Scan on hp2 hp_3 Filter: (a = 1) -> Seq Scan on hp3 hp_4 Filter: (a = 1) (9 rows) explain (costs off) select * from hp where b = 'xxx'; QUERY PLAN ----------------------------------- Append -> Seq Scan on hp0 hp_1 Filter: (b = 'xxx'::text) -> Seq Scan on hp1 hp_2 Filter: (b = 'xxx'::text) -> Seq Scan on hp2 hp_3 Filter: (b = 'xxx'::text) -> Seq Scan on hp3 hp_4 Filter: (b = 'xxx'::text) (9 rows) explain (costs off) select * from hp where a is null; QUERY PLAN ----------------------------- Append -> Seq Scan on hp0 hp_1 Filter: (a IS NULL) -> Seq Scan on hp1 hp_2 Filter: (a IS NULL) -> Seq Scan on hp2 hp_3 Filter: (a IS NULL) -> Seq Scan on hp3 hp_4 Filter: (a IS NULL) (9 rows) explain (costs off) select * from hp where b is null; QUERY PLAN ----------------------------- Append -> Seq Scan on hp0 hp_1 Filter: (b IS NULL) -> Seq Scan on hp1 hp_2 Filter: (b IS NULL) -> Seq Scan on hp2 hp_3 Filter: (b IS NULL) -> Seq Scan on hp3 hp_4 Filter: (b IS NULL) (9 rows) explain (costs off) select * from hp where a < 1 and b = 'xxx'; QUERY PLAN ------------------------------------------------- Append -> Seq Scan on hp0 hp_1 Filter: ((a < 1) AND (b = 'xxx'::text)) -> Seq Scan on hp1 hp_2 Filter: ((a < 1) AND (b = 'xxx'::text)) -> Seq Scan on hp2 hp_3 Filter: ((a < 1) AND (b = 'xxx'::text)) -> Seq Scan on hp3 hp_4 Filter: ((a < 1) AND (b = 'xxx'::text)) (9 rows) explain (costs off) select * from hp where a <> 1 and b = 'yyy'; QUERY PLAN -------------------------------------------------- Append -> Seq Scan on hp0 hp_1 Filter: ((a <> 1) AND (b = 'yyy'::text)) -> Seq Scan on hp1 hp_2 Filter: ((a <> 1) AND (b = 'yyy'::text)) -> Seq Scan on hp2 hp_3 Filter: ((a <> 1) AND (b = 'yyy'::text)) -> Seq Scan on hp3 hp_4 Filter: ((a <> 1) AND (b = 'yyy'::text)) (9 rows) explain (costs off) select * from hp where a <> 1 and b <> 'xxx'; QUERY PLAN --------------------------------------------------- Append -> Seq Scan on hp0 hp_1 Filter: ((a <> 1) AND (b <> 'xxx'::text)) -> Seq Scan on hp1 hp_2 Filter: ((a <> 1) AND (b <> 'xxx'::text)) -> Seq Scan on hp2 hp_3 Filter: ((a <> 1) AND (b <> 'xxx'::text)) -> Seq Scan on hp3 hp_4 Filter: ((a <> 1) AND (b <> 'xxx'::text)) (9 rows) -- 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 is null and b is null; QUERY PLAN ----------------------------------------- Seq Scan on hp0 hp Filter: ((a IS NULL) AND (b IS NULL)) (2 rows) explain (costs off) select * from hp where a = 1 and b is null; QUERY PLAN ------------------------------------- Seq Scan on hp1 hp Filter: ((b IS NULL) AND (a = 1)) (2 rows) explain (costs off) select * from hp where a = 1 and b = 'xxx'; QUERY PLAN ------------------------------------------- Seq Scan on hp0 hp Filter: ((a = 1) AND (b = 'xxx'::text)) (2 rows) explain (costs off) select * from hp where a is null and b = 'xxx'; QUERY PLAN ----------------------------------------------- Seq Scan on hp2 hp Filter: ((a IS NULL) AND (b = 'xxx'::text)) (2 rows) explain (costs off) select * from hp where a = 2 and b = 'xxx'; QUERY PLAN ------------------------------------------- Seq Scan on hp3 hp Filter: ((a = 2) AND (b = 'xxx'::text)) (2 rows) explain (costs off) select * from hp where a = 1 and b = 'abcde'; QUERY PLAN --------------------------------------------- Seq Scan on hp2 hp Filter: ((a = 1) AND (b = 'abcde'::text)) (2 rows) explain (costs off) select * from hp where (a = 1 and b = 'abcde') or (a = 2 and b = 'xxx') or (a is null and b is null); QUERY PLAN ------------------------------------------------------------------------------------------------------------------------- Append -> Seq Scan on hp0 hp_1 Filter: (((a = 1) AND (b = 'abcde'::text)) OR ((a = 2) AND (b = 'xxx'::text)) OR ((a IS NULL) AND (b IS NULL))) -> Seq Scan on hp2 hp_2 Filter: (((a = 1) AND (b = 'abcde'::text)) OR ((a = 2) AND (b = 'xxx'::text)) OR ((a IS NULL) AND (b IS NULL))) -> Seq Scan on hp3 hp_3 Filter: (((a = 1) AND (b = 'abcde'::text)) OR ((a = 2) AND (b = 'xxx'::text)) OR ((a IS NULL) AND (b IS NULL))) (7 rows) -- test pruning when not all the partitions exist drop table hp1; drop table hp3; explain (costs off) select * from hp where a = 1 and b = 'abcde'; QUERY PLAN --------------------------------------------- Seq Scan on hp2 hp Filter: ((a = 1) AND (b = 'abcde'::text)) (2 rows) explain (costs off) select * from hp where a = 1 and b = 'abcde' and (c = 2 or c = 3); QUERY PLAN ---------------------------------------------------------------------- Seq Scan on hp2 hp Filter: ((a = 1) AND (b = 'abcde'::text) AND ((c = 2) OR (c = 3))) (2 rows) drop table hp2; explain (costs off) select * from hp where a = 1 and b = 'abcde' and (c = 2 or c = 3); QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) -- -- Test runtime partition pruning -- create table ab (a int not null, b int not null) partition by list (a); create table ab_a2 partition of ab for values in(2) partition by list (b); create table ab_a2_b1 partition of ab_a2 for values in (1); create table ab_a2_b2 partition of ab_a2 for values in (2); create table ab_a2_b3 partition of ab_a2 for values in (3); create table ab_a1 partition of ab for values in(1) partition by list (b); create table ab_a1_b1 partition of ab_a1 for values in (1); create table ab_a1_b2 partition of ab_a1 for values in (2); create table ab_a1_b3 partition of ab_a1 for values in (3); create table ab_a3 partition of ab for values in(3) partition by list (b); create table ab_a3_b1 partition of ab_a3 for values in (1); create table ab_a3_b2 partition of ab_a3 for values in (2); create table ab_a3_b3 partition of ab_a3 for values in (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 $1 and $2 and b <= $3; explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2, 3); QUERY PLAN --------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 6 -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) -> Seq Scan on ab_a2_b3 ab_3 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) (8 rows) explain (analyze, costs off, summary off, timing off) execute ab_q1 (1, 2, 3); QUERY PLAN --------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 3 -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) -> Seq Scan on ab_a1_b2 ab_2 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) -> Seq Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) -> Seq Scan on ab_a2_b1 ab_4 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) -> Seq Scan on ab_a2_b2 ab_5 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) -> Seq Scan on ab_a2_b3 ab_6 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) (14 rows) deallocate ab_q1; -- Runtime pruning after optimizer pruning prepare ab_q1 (int, int) as select a from ab where a between $1 and $2 and b < 3; explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2); QUERY PLAN --------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 4 -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) (6 rows) explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 4); QUERY PLAN --------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 2 -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) -> Seq Scan on ab_a3_b1 ab_3 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) -> Seq Scan on ab_a3_b2 ab_4 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) (10 rows) -- 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 $1 and $2 and b < (select 3); explain (analyze, costs off, summary off, timing off) execute ab_q2 (2, 2); QUERY PLAN ----------------------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 6 InitPlan 1 -> Result (actual rows=1 loops=1) -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b < (InitPlan 1).col1)) -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) Filter: ((a >= $1) AND (a <= $2) AND (b < (InitPlan 1).col1)) -> Seq Scan on ab_a2_b3 ab_3 (never executed) Filter: ((a >= $1) AND (a <= $2) AND (b < (InitPlan 1).col1)) (10 rows) -- 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 $1 and $2 and a < (select 3); explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2); QUERY PLAN ----------------------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 6 InitPlan 1 -> Result (actual rows=1 loops=1) -> Seq Scan on ab_a1_b2 ab_1 (actual rows=0 loops=1) Filter: ((b >= $1) AND (b <= $2) AND (a < (InitPlan 1).col1)) -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) Filter: ((b >= $1) AND (b <= $2) AND (a < (InitPlan 1).col1)) -> Seq Scan on ab_a3_b2 ab_3 (never executed) Filter: ((b >= $1) AND (b <= $2) AND (a < (InitPlan 1).col1)) (10 rows) -- -- Test runtime pruning with hash partitioned tables -- -- recreate partitions dropped above create table hp1 partition of hp for values with (modulus 4, remainder 1); create table hp2 partition of hp for values with (modulus 4, remainder 2); create table hp3 partition of hp for values with (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 is null and b = $1; explain (costs off) execute hp_q1('xxx'); QUERY PLAN -------------------------------------------- Append Subplans Removed: 3 -> Seq Scan on hp2 hp_1 Filter: ((a IS NULL) AND (b = $1)) (4 rows) deallocate hp_q1; drop table hp; -- Test a backwards Append scan create table list_part (a int) partition by list (a); create table list_part1 partition of list_part for values in (1); create table list_part2 partition of list_part for values in (2); create table list_part3 partition of list_part for values in (3); create table list_part4 partition of list_part for values in (4); insert into 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 CURSOR for select 1 from list_part where a > (select 1) and a < (select 4); -- move beyond the final row move 3 from cur; -- Ensure we get two rows. fetch backward all from cur; ?column? ---------- 1 1 (2 rows) commit; begin; -- Test run-time pruning using stable functions create function list_part_fn(int) returns int as $$ 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) select * from list_part where a = list_part_fn(1); QUERY PLAN ------------------------------------------------------------------ Append (actual rows=1 loops=1) Subplans Removed: 3 -> Seq Scan on list_part1 list_part_1 (actual rows=1 loops=1) Filter: (a = list_part_fn(1)) (4 rows) -- Ensure pruning does not take place when the function has a Var parameter explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(a); QUERY PLAN ------------------------------------------------------------------ Append (actual rows=4 loops=1) -> Seq Scan on list_part1 list_part_1 (actual rows=1 loops=1) Filter: (a = list_part_fn(a)) -> Seq Scan on list_part2 list_part_2 (actual rows=1 loops=1) Filter: (a = list_part_fn(a)) -> Seq Scan on list_part3 list_part_3 (actual rows=1 loops=1) Filter: (a = list_part_fn(a)) -> Seq Scan on list_part4 list_part_4 (actual rows=1 loops=1) Filter: (a = list_part_fn(a)) (9 rows) -- Ensure pruning does not take place when the expression contains a Var. explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1) + a; QUERY PLAN ------------------------------------------------------------------ Append (actual rows=0 loops=1) -> Seq Scan on list_part1 list_part_1 (actual rows=0 loops=1) Filter: (a = (list_part_fn(1) + a)) Rows Removed by Filter: 1 -> Seq Scan on list_part2 list_part_2 (actual rows=0 loops=1) Filter: (a = (list_part_fn(1) + a)) Rows Removed by Filter: 1 -> Seq Scan on list_part3 list_part_3 (actual rows=0 loops=1) Filter: (a = (list_part_fn(1) + a)) Rows Removed by Filter: 1 -> Seq Scan on list_part4 list_part_4 (actual rows=0 loops=1) Filter: (a = (list_part_fn(1) + a)) Rows Removed by Filter: 1 (13 rows) rollback; drop table 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) %s', $1) loop ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); ln := regexp_replace(ln, 'Rows Removed by Filter: \d+', 'Rows Removed by Filter: N'); return next ln; end loop; end; $$; prepare ab_q4 (int, int) as select avg(a) from ab where a between $1 and $2 and 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; select explain_parallel_append('execute ab_q4 (2, 2)'); explain_parallel_append ------------------------------------------------------------------------------------ Finalize Aggregate (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 2 Workers Launched: N -> Partial Aggregate (actual rows=N loops=N) -> Parallel Append (actual rows=N loops=N) Subplans Removed: 6 -> Parallel Seq Scan on ab_a2_b1 ab_1 (actual rows=N loops=N) Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) -> Parallel Seq Scan on ab_a2_b2 ab_2 (actual rows=N loops=N) Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) -> Parallel Seq Scan on ab_a2_b3 ab_3 (actual rows=N loops=N) Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) (13 rows) -- Test run-time pruning with IN lists. prepare ab_q5 (int, int, int) as select avg(a) from ab where a in($1,$2,$3) and b < 4; select explain_parallel_append('execute ab_q5 (1, 1, 1)'); explain_parallel_append ------------------------------------------------------------------------------------ Finalize Aggregate (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 2 Workers Launched: N -> Partial Aggregate (actual rows=N loops=N) -> Parallel Append (actual rows=N loops=N) Subplans Removed: 6 -> Parallel Seq Scan on ab_a1_b1 ab_1 (actual rows=N loops=N) Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) -> Parallel Seq Scan on ab_a1_b2 ab_2 (actual rows=N loops=N) Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) -> Parallel Seq Scan on ab_a1_b3 ab_3 (actual rows=N loops=N) Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) (13 rows) select explain_parallel_append('execute ab_q5 (2, 3, 3)'); explain_parallel_append ------------------------------------------------------------------------------------ Finalize Aggregate (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 2 Workers Launched: N -> Partial Aggregate (actual rows=N loops=N) -> Parallel Append (actual rows=N loops=N) Subplans Removed: 3 -> Parallel Seq Scan on ab_a2_b1 ab_1 (actual rows=N loops=N) Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) -> Parallel Seq Scan on ab_a2_b2 ab_2 (actual rows=N loops=N) Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) -> Parallel Seq Scan on ab_a2_b3 ab_3 (actual rows=N loops=N) Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) -> Parallel Seq Scan on ab_a3_b1 ab_4 (actual rows=N loops=N) Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) -> Parallel Seq Scan on ab_a3_b2 ab_5 (actual rows=N loops=N) Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) -> Parallel Seq Scan on ab_a3_b3 ab_6 (actual rows=N loops=N) Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) (19 rows) -- Try some params whose values do not belong to any partition. select explain_parallel_append('execute ab_q5 (33, 44, 55)'); explain_parallel_append ----------------------------------------------------------- Finalize Aggregate (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 2 Workers Launched: N -> Partial Aggregate (actual rows=N loops=N) -> Parallel Append (actual rows=N loops=N) Subplans Removed: 9 (7 rows) -- 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'); explain_parallel_append ------------------------------------------------------------------------------------------------ Aggregate (actual rows=N loops=N) InitPlan 1 -> Result (actual rows=N loops=N) InitPlan 2 -> Result (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 2 Workers Launched: N -> Parallel Append (actual rows=N loops=N) -> Parallel Seq Scan on ab_a1_b2 ab_1 (actual rows=N loops=N) Filter: ((b = 2) AND ((a = (InitPlan 1).col1) OR (a = (InitPlan 2).col1))) -> Parallel Seq Scan on ab_a2_b2 ab_2 (never executed) Filter: ((b = 2) AND ((a = (InitPlan 1).col1) OR (a = (InitPlan 2).col1))) -> Parallel Seq Scan on ab_a3_b2 ab_3 (actual rows=N loops=N) Filter: ((b = 2) AND ((a = (InitPlan 1).col1) OR (a = (InitPlan 2).col1))) (15 rows) -- Test pruning during parallel nested loop query create table lprt_a (a int not null); -- Insert some values we won't find in ab insert into lprt_a select 0 from generate_series(1,100); -- and insert some values that we should find. insert into lprt_a values(1),(1); analyze lprt_a; create index ab_a2_b1_a_idx on ab_a2_b1 (a); create index ab_a2_b2_a_idx on ab_a2_b2 (a); create index ab_a2_b3_a_idx on ab_a2_b3 (a); create index ab_a1_b1_a_idx on ab_a1_b1 (a); create index ab_a1_b2_a_idx on ab_a1_b2 (a); create index ab_a1_b3_a_idx on ab_a1_b3 (a); create index ab_a3_b1_a_idx on ab_a3_b1 (a); create index ab_a3_b2_a_idx on ab_a3_b2 (a); create index ab_a3_b3_a_idx on ab_a3_b3 (a); set enable_hashjoin = 0; set enable_mergejoin = 0; set enable_memoize = 0; -- Temporarily install some debugging to investigate plan instability. select c.relname,c.relpages,c.reltuples,i.indisvalid,s.autovacuum_count,s.autoanalyze_count from pg_class c left join pg_stat_all_tables s on c.oid = s.relid left join pg_index i on c.oid = i.indexrelid where c.relname like 'ab\_%' order by c.relname; relname | relpages | reltuples | indisvalid | autovacuum_count | autoanalyze_count ----------------+----------+-----------+------------+------------------+------------------- ab_a1 | 0 | -1 | | 0 | 0 ab_a1_b1 | 0 | -1 | | 0 | 0 ab_a1_b1_a_idx | 1 | 0 | t | | ab_a1_b2 | 0 | -1 | | 0 | 0 ab_a1_b2_a_idx | 1 | 0 | t | | ab_a1_b3 | 0 | -1 | | 0 | 0 ab_a1_b3_a_idx | 1 | 0 | t | | ab_a2 | 0 | -1 | | 0 | 0 ab_a2_b1 | 0 | -1 | | 0 | 0 ab_a2_b1_a_idx | 1 | 0 | t | | ab_a2_b2 | 0 | -1 | | 0 | 0 ab_a2_b2_a_idx | 1 | 0 | t | | ab_a2_b3 | 0 | -1 | | 0 | 0 ab_a2_b3_a_idx | 1 | 0 | t | | ab_a3 | 0 | -1 | | 0 | 0 ab_a3_b1 | 0 | -1 | | 0 | 0 ab_a3_b1_a_idx | 1 | 0 | t | | ab_a3_b2 | 0 | -1 | | 0 | 0 ab_a3_b2_a_idx | 1 | 0 | t | | ab_a3_b3 | 0 | -1 | | 0 | 0 ab_a3_b3_a_idx | 1 | 0 | t | | (21 rows) 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)'); explain_parallel_append -------------------------------------------------------------------------------------------------------- Finalize Aggregate (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 1 Workers Launched: N -> Partial Aggregate (actual rows=N loops=N) -> Nested Loop (actual rows=N loops=N) -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{0,0,1}'::integer[])) -> Append (actual rows=N loops=N) -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N) Index Cond: (a = a.a) -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) Index Cond: (a = a.a) -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) Index Cond: (a = a.a) -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) Index Cond: (a = a.a) (27 rows) -- 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)'); explain_parallel_append -------------------------------------------------------------------------------------------------------- Finalize Aggregate (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 1 Workers Launched: N -> Partial Aggregate (actual rows=N loops=N) -> Nested Loop (actual rows=N loops=N) -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{0,0,1}'::integer[])) -> Append (actual rows=N loops=N) -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N) Index Cond: (a = (a.a + 0)) -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) Index Cond: (a = (a.a + 0)) -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) Index Cond: (a = (a.a + 0)) -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) Index Cond: (a = (a.a + 0)) -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) Index Cond: (a = (a.a + 0)) -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) Index Cond: (a = (a.a + 0)) -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) Index Cond: (a = (a.a + 0)) -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) Index Cond: (a = (a.a + 0)) -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) Index Cond: (a = (a.a + 0)) (27 rows) insert into 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)'); explain_parallel_append -------------------------------------------------------------------------------------------------------- Finalize Aggregate (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 1 Workers Launched: N -> Partial Aggregate (actual rows=N loops=N) -> Nested Loop (actual rows=N loops=N) -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{1,0,3}'::integer[])) -> Append (actual rows=N loops=N) -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N) Index Cond: (a = a.a) -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) Index Cond: (a = a.a) -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) Index Cond: (a = a.a) -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (actual rows=N loops=N) Index Cond: (a = a.a) -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (actual rows=N loops=N) Index Cond: (a = a.a) -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (actual rows=N loops=N) Index Cond: (a = a.a) (27 rows) 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)'); explain_parallel_append -------------------------------------------------------------------------------------------------------- Finalize Aggregate (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 1 Workers Launched: N -> Partial Aggregate (actual rows=N loops=N) -> Nested Loop (actual rows=N loops=N) -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{1,0,0}'::integer[])) Rows Removed by Filter: N -> Append (actual rows=N loops=N) -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N) Index Cond: (a = a.a) -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) Index Cond: (a = a.a) -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) Index Cond: (a = a.a) -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) Index Cond: (a = a.a) (28 rows) delete from 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)'); explain_parallel_append ------------------------------------------------------------------------------------------------- Finalize Aggregate (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 1 Workers Launched: N -> Partial Aggregate (actual rows=N loops=N) -> Nested Loop (actual rows=N loops=N) -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{1,0,0}'::integer[])) Rows Removed by Filter: N -> Append (actual rows=N loops=N) -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) Index Cond: (a = a.a) -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) Index Cond: (a = a.a) (28 rows) reset enable_hashjoin; reset enable_mergejoin; reset enable_memoize; reset parallel_setup_cost; reset parallel_tuple_cost; reset min_parallel_table_scan_size; reset max_parallel_workers_per_gather; -- Test run-time partition pruning with an initplan explain (analyze, costs off, summary off, timing off) select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1 from lprt_a); QUERY PLAN ------------------------------------------------------------------------- Append (actual rows=0 loops=1) InitPlan 1 -> Aggregate (actual rows=1 loops=1) -> Seq Scan on lprt_a (actual rows=102 loops=1) InitPlan 2 -> Aggregate (actual rows=1 loops=1) -> Seq Scan on lprt_a lprt_a_1 (actual rows=102 loops=1) -> Bitmap Heap Scan on ab_a1_b1 ab_1 (never executed) Recheck Cond: (a = (InitPlan 1).col1) Filter: (b = (InitPlan 2).col1) -> Bitmap Index Scan on ab_a1_b1_a_idx (never executed) Index Cond: (a = (InitPlan 1).col1) -> Bitmap Heap Scan on ab_a1_b2 ab_2 (never executed) Recheck Cond: (a = (InitPlan 1).col1) Filter: (b = (InitPlan 2).col1) -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed) Index Cond: (a = (InitPlan 1).col1) -> Bitmap Heap Scan on ab_a1_b3 ab_3 (never executed) Recheck Cond: (a = (InitPlan 1).col1) Filter: (b = (InitPlan 2).col1) -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed) Index Cond: (a = (InitPlan 1).col1) -> Bitmap Heap Scan on ab_a2_b1 ab_4 (never executed) Recheck Cond: (a = (InitPlan 1).col1) Filter: (b = (InitPlan 2).col1) -> Bitmap Index Scan on ab_a2_b1_a_idx (never executed) Index Cond: (a = (InitPlan 1).col1) -> Bitmap Heap Scan on ab_a2_b2 ab_5 (never executed) Recheck Cond: (a = (InitPlan 1).col1) Filter: (b = (InitPlan 2).col1) -> Bitmap Index Scan on ab_a2_b2_a_idx (never executed) Index Cond: (a = (InitPlan 1).col1) -> Bitmap Heap Scan on ab_a2_b3 ab_6 (never executed) Recheck Cond: (a = (InitPlan 1).col1) Filter: (b = (InitPlan 2).col1) -> Bitmap Index Scan on ab_a2_b3_a_idx (never executed) Index Cond: (a = (InitPlan 1).col1) -> Bitmap Heap Scan on ab_a3_b1 ab_7 (never executed) Recheck Cond: (a = (InitPlan 1).col1) Filter: (b = (InitPlan 2).col1) -> Bitmap Index Scan on ab_a3_b1_a_idx (never executed) Index Cond: (a = (InitPlan 1).col1) -> Bitmap Heap Scan on ab_a3_b2 ab_8 (actual rows=0 loops=1) Recheck Cond: (a = (InitPlan 1).col1) Filter: (b = (InitPlan 2).col1) -> Bitmap Index Scan on ab_a3_b2_a_idx (actual rows=0 loops=1) Index Cond: (a = (InitPlan 1).col1) -> Bitmap Heap Scan on ab_a3_b3 ab_9 (never executed) Recheck Cond: (a = (InitPlan 1).col1) Filter: (b = (InitPlan 2).col1) -> Bitmap Index Scan on ab_a3_b3_a_idx (never executed) Index Cond: (a = (InitPlan 1).col1) (52 rows) -- Test run-time partition pruning with UNION ALL parents explain (analyze, costs off, summary off, timing off) select * from (select * from ab where a = 1 union all select * from ab) ab where b = (select 1); QUERY PLAN ------------------------------------------------------------------------------- Append (actual rows=0 loops=1) InitPlan 1 -> Result (actual rows=1 loops=1) -> Append (actual rows=0 loops=1) -> Bitmap Heap Scan on ab_a1_b1 ab_11 (actual rows=0 loops=1) Recheck Cond: (a = 1) Filter: (b = (InitPlan 1).col1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) -> Bitmap Heap Scan on ab_a1_b2 ab_12 (never executed) Recheck Cond: (a = 1) Filter: (b = (InitPlan 1).col1) -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed) Index Cond: (a = 1) -> Bitmap Heap Scan on ab_a1_b3 ab_13 (never executed) Recheck Cond: (a = 1) Filter: (b = (InitPlan 1).col1) -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed) Index Cond: (a = 1) -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a1_b2 ab_2 (never executed) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a1_b3 ab_3 (never executed) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a2_b1 ab_4 (actual rows=0 loops=1) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a2_b2 ab_5 (never executed) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a2_b3 ab_6 (never executed) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a3_b1 ab_7 (actual rows=0 loops=1) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a3_b2 ab_8 (never executed) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a3_b3 ab_9 (never executed) Filter: (b = (InitPlan 1).col1) (37 rows) -- A case containing a UNION ALL with a non-partitioned child. explain (analyze, costs off, summary off, timing off) select * from (select * from ab where a = 1 union all (values(10,5)) union all select * from ab) ab where b = (select 1); QUERY PLAN ------------------------------------------------------------------------------- Append (actual rows=0 loops=1) InitPlan 1 -> Result (actual rows=1 loops=1) -> Append (actual rows=0 loops=1) -> Bitmap Heap Scan on ab_a1_b1 ab_11 (actual rows=0 loops=1) Recheck Cond: (a = 1) Filter: (b = (InitPlan 1).col1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) -> Bitmap Heap Scan on ab_a1_b2 ab_12 (never executed) Recheck Cond: (a = 1) Filter: (b = (InitPlan 1).col1) -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed) Index Cond: (a = 1) -> Bitmap Heap Scan on ab_a1_b3 ab_13 (never executed) Recheck Cond: (a = 1) Filter: (b = (InitPlan 1).col1) -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed) Index Cond: (a = 1) -> Result (actual rows=0 loops=1) One-Time Filter: (5 = (InitPlan 1).col1) -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a1_b2 ab_2 (never executed) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a1_b3 ab_3 (never executed) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a2_b1 ab_4 (actual rows=0 loops=1) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a2_b2 ab_5 (never executed) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a2_b3 ab_6 (never executed) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a3_b1 ab_7 (actual rows=0 loops=1) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a3_b2 ab_8 (never executed) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a3_b3 ab_9 (never executed) Filter: (b = (InitPlan 1).col1) (39 rows) -- Another UNION ALL test, but containing a mix of exec init and exec run-time pruning. create table xy_1 (x int, y int); insert into 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 union all select tableoid::regclass,x,y from xy_1 union all select tableoid::regclass,a,b from ab ) ab where a = $1 and b = (select -10); -- Ensure the xy_1 subplan is not pruned. explain (analyze, costs off, summary off, timing off) execute ab_q6(1); QUERY PLAN -------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 12 InitPlan 1 -> Result (actual rows=1 loops=1) -> Seq Scan on ab_a1_b1 ab_1 (never executed) Filter: ((a = $1) AND (b = (InitPlan 1).col1)) -> Seq Scan on ab_a1_b2 ab_2 (never executed) Filter: ((a = $1) AND (b = (InitPlan 1).col1)) -> Seq Scan on ab_a1_b3 ab_3 (never executed) Filter: ((a = $1) AND (b = (InitPlan 1).col1)) -> Seq Scan on xy_1 (actual rows=0 loops=1) Filter: ((x = $1) AND (y = (InitPlan 1).col1)) Rows Removed by Filter: 1 -> Seq Scan on ab_a1_b1 ab_4 (never executed) Filter: ((a = $1) AND (b = (InitPlan 1).col1)) -> Seq Scan on ab_a1_b2 ab_5 (never executed) Filter: ((a = $1) AND (b = (InitPlan 1).col1)) -> Seq Scan on ab_a1_b3 ab_6 (never executed) Filter: ((a = $1) AND (b = (InitPlan 1).col1)) (19 rows) -- Ensure we see just the xy_1 row. execute ab_q6(100); tableoid | a | b ----------+-----+----- xy_1 | 100 | -10 (1 row) reset enable_bitmapscan; reset enable_indexscan; deallocate ab_q1; deallocate ab_q2; deallocate ab_q3; deallocate ab_q4; deallocate ab_q5; deallocate ab_q6; -- Temporarily install some debugging to investigate plan instability. select c.relname,c.relpages,c.reltuples,i.indisvalid,s.autovacuum_count,s.autoanalyze_count from pg_class c left join pg_stat_all_tables s on c.oid = s.relid left join pg_index i on c.oid = i.indexrelid where c.relname like 'ab\_%' order by c.relname; relname | relpages | reltuples | indisvalid | autovacuum_count | autoanalyze_count ----------------+----------+-----------+------------+------------------+------------------- ab_a1 | 0 | -1 | | 0 | 0 ab_a1_b1 | 0 | -1 | | 0 | 0 ab_a1_b1_a_idx | 1 | 0 | t | | ab_a1_b2 | 0 | -1 | | 0 | 0 ab_a1_b2_a_idx | 1 | 0 | t | | ab_a1_b3 | 0 | -1 | | 0 | 0 ab_a1_b3_a_idx | 1 | 0 | t | | ab_a2 | 0 | -1 | | 0 | 0 ab_a2_b1 | 0 | -1 | | 0 | 0 ab_a2_b1_a_idx | 1 | 0 | t | | ab_a2_b2 | 0 | -1 | | 0 | 0 ab_a2_b2_a_idx | 1 | 0 | t | | ab_a2_b3 | 0 | -1 | | 0 | 0 ab_a2_b3_a_idx | 1 | 0 | t | | ab_a3 | 0 | -1 | | 0 | 0 ab_a3_b1 | 0 | -1 | | 0 | 0 ab_a3_b1_a_idx | 1 | 0 | t | | ab_a3_b2 | 0 | -1 | | 0 | 0 ab_a3_b2_a_idx | 1 | 0 | t | | ab_a3_b3 | 0 | -1 | | 0 | 0 ab_a3_b3_a_idx | 1 | 0 | t | | (21 rows) -- UPDATE on a partition subtree has been seen to have problems. insert into ab values (1,2); explain (analyze, costs off, summary off, timing off) update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; QUERY PLAN ------------------------------------------------------------------------------------------- Update on ab_a1 (actual rows=0 loops=1) Update on ab_a1_b1 ab_a1_1 Update on ab_a1_b2 ab_a1_2 Update on ab_a1_b3 ab_a1_3 -> Nested Loop (actual rows=1 loops=1) -> Append (actual rows=1 loops=1) -> Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) -> Bitmap Heap Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) -> Bitmap Heap Scan on ab_a1_b3 ab_a1_3 (actual rows=0 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) -> Materialize (actual rows=1 loops=1) -> Append (actual rows=1 loops=1) -> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) -> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) (36 rows) table ab; a | b ---+--- 1 | 3 (1 row) -- Test UPDATE where source relation has run-time pruning enabled truncate ab; insert into ab values (1, 1), (1, 2), (1, 3), (2, 1); explain (analyze, costs off, summary off, timing off) update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1); QUERY PLAN ------------------------------------------------------------------------------ Update on ab_a1 (actual rows=0 loops=1) Update on ab_a1_b1 ab_a1_1 Update on ab_a1_b2 ab_a1_2 Update on ab_a1_b3 ab_a1_3 InitPlan 1 -> Result (actual rows=1 loops=1) -> Nested Loop (actual rows=3 loops=1) -> Append (actual rows=3 loops=1) -> Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1) -> Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1) -> Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1) -> Materialize (actual rows=1 loops=3) -> Append (actual rows=1 loops=1) -> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a2_b2 ab_a2_2 (never executed) Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a2_b3 ab_a2_3 (never executed) Filter: (b = (InitPlan 1).col1) (19 rows) select tableoid::regclass, * from ab; tableoid | a | b ----------+---+--- ab_a1_b3 | 1 | 3 ab_a1_b3 | 1 | 3 ab_a1_b3 | 1 | 3 ab_a2_b1 | 2 | 1 (4 rows) drop table ab, lprt_a; -- Join create table tbl1(col1 int); insert into tbl1 values (501), (505); -- Basic table create table tprt (col1 int) partition by range (col1); create table tprt_1 partition of tprt for values from (1) to (501); create table tprt_2 partition of tprt for values from (501) to (1001); create table tprt_3 partition of tprt for values from (1001) to (2001); create table tprt_4 partition of tprt for values from (2001) to (3001); create table tprt_5 partition of tprt for values from (3001) to (4001); create table tprt_6 partition of tprt for values from (4001) to (5001); create index tprt1_idx on tprt_1 (col1); create index tprt2_idx on tprt_2 (col1); create index tprt3_idx on tprt_3 (col1); create index tprt4_idx on tprt_4 (col1); create index tprt5_idx on tprt_5 (col1); create index tprt6_idx on tprt_6 (col1); insert into tprt values (10), (20), (501), (502), (505), (1001), (4500); set enable_hashjoin = off; set enable_mergejoin = off; explain (analyze, costs off, summary off, timing off) select * from tbl1 join tprt on tbl1.col1 > tprt.col1; QUERY PLAN -------------------------------------------------------------------------- Nested Loop (actual rows=6 loops=1) -> Seq Scan on tbl1 (actual rows=2 loops=1) -> Append (actual rows=3 loops=2) -> Index Scan using tprt1_idx on tprt_1 (actual rows=2 loops=2) Index Cond: (col1 < tbl1.col1) -> Index Scan using tprt2_idx on tprt_2 (actual rows=2 loops=1) Index Cond: (col1 < tbl1.col1) -> Index Scan using tprt3_idx on tprt_3 (never executed) Index Cond: (col1 < tbl1.col1) -> Index Scan using tprt4_idx on tprt_4 (never executed) Index Cond: (col1 < tbl1.col1) -> Index Scan using tprt5_idx on tprt_5 (never executed) Index Cond: (col1 < tbl1.col1) -> Index Scan using tprt6_idx on tprt_6 (never executed) Index Cond: (col1 < tbl1.col1) (15 rows) explain (analyze, costs off, summary off, timing off) select * from tbl1 join tprt on tbl1.col1 = tprt.col1; QUERY PLAN -------------------------------------------------------------------------- Nested Loop (actual rows=2 loops=1) -> Seq Scan on tbl1 (actual rows=2 loops=1) -> Append (actual rows=1 loops=2) -> Index Scan using tprt1_idx on tprt_1 (never executed) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt2_idx on tprt_2 (actual rows=1 loops=2) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt3_idx on tprt_3 (never executed) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt4_idx on tprt_4 (never executed) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt5_idx on tprt_5 (never executed) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt6_idx on tprt_6 (never executed) Index Cond: (col1 = tbl1.col1) (15 rows) select tbl1.col1, tprt.col1 from tbl1 inner join tprt on tbl1.col1 > tprt.col1 order by tbl1.col1, tprt.col1; col1 | col1 ------+------ 501 | 10 501 | 20 505 | 10 505 | 20 505 | 501 505 | 502 (6 rows) select tbl1.col1, tprt.col1 from tbl1 inner join tprt on tbl1.col1 = tprt.col1 order by tbl1.col1, tprt.col1; col1 | col1 ------+------ 501 | 501 505 | 505 (2 rows) -- Multiple partitions insert into tbl1 values (1001), (1010), (1011); explain (analyze, costs off, summary off, timing off) select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1; QUERY PLAN -------------------------------------------------------------------------- Nested Loop (actual rows=23 loops=1) -> Seq Scan on tbl1 (actual rows=5 loops=1) -> Append (actual rows=5 loops=5) -> Index Scan using tprt1_idx on tprt_1 (actual rows=2 loops=5) Index Cond: (col1 < tbl1.col1) -> Index Scan using tprt2_idx on tprt_2 (actual rows=3 loops=4) Index Cond: (col1 < tbl1.col1) -> Index Scan using tprt3_idx on tprt_3 (actual rows=1 loops=2) Index Cond: (col1 < tbl1.col1) -> Index Scan using tprt4_idx on tprt_4 (never executed) Index Cond: (col1 < tbl1.col1) -> Index Scan using tprt5_idx on tprt_5 (never executed) Index Cond: (col1 < tbl1.col1) -> Index Scan using tprt6_idx on tprt_6 (never executed) Index Cond: (col1 < tbl1.col1) (15 rows) explain (analyze, costs off, summary off, timing off) select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1; QUERY PLAN -------------------------------------------------------------------------- Nested Loop (actual rows=3 loops=1) -> Seq Scan on tbl1 (actual rows=5 loops=1) -> Append (actual rows=1 loops=5) -> Index Scan using tprt1_idx on tprt_1 (never executed) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt2_idx on tprt_2 (actual rows=1 loops=2) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt3_idx on tprt_3 (actual rows=0 loops=3) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt4_idx on tprt_4 (never executed) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt5_idx on tprt_5 (never executed) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt6_idx on tprt_6 (never executed) Index Cond: (col1 = tbl1.col1) (15 rows) select tbl1.col1, tprt.col1 from tbl1 inner join tprt on tbl1.col1 > tprt.col1 order by tbl1.col1, tprt.col1; col1 | col1 ------+------ 501 | 10 501 | 20 505 | 10 505 | 20 505 | 501 505 | 502 1001 | 10 1001 | 20 1001 | 501 1001 | 502 1001 | 505 1010 | 10 1010 | 20 1010 | 501 1010 | 502 1010 | 505 1010 | 1001 1011 | 10 1011 | 20 1011 | 501 1011 | 502 1011 | 505 1011 | 1001 (23 rows) select tbl1.col1, tprt.col1 from tbl1 inner join tprt on tbl1.col1 = tprt.col1 order by tbl1.col1, tprt.col1; col1 | col1 ------+------ 501 | 501 505 | 505 1001 | 1001 (3 rows) -- Last partition delete from tbl1; insert into tbl1 values (4400); explain (analyze, costs off, summary off, timing off) select * from tbl1 join tprt on tbl1.col1 < tprt.col1; QUERY PLAN -------------------------------------------------------------------------- Nested Loop (actual rows=1 loops=1) -> Seq Scan on tbl1 (actual rows=1 loops=1) -> Append (actual rows=1 loops=1) -> Index Scan using tprt1_idx on tprt_1 (never executed) Index Cond: (col1 > tbl1.col1) -> Index Scan using tprt2_idx on tprt_2 (never executed) Index Cond: (col1 > tbl1.col1) -> Index Scan using tprt3_idx on tprt_3 (never executed) Index Cond: (col1 > tbl1.col1) -> Index Scan using tprt4_idx on tprt_4 (never executed) Index Cond: (col1 > tbl1.col1) -> Index Scan using tprt5_idx on tprt_5 (never executed) Index Cond: (col1 > tbl1.col1) -> Index Scan using tprt6_idx on tprt_6 (actual rows=1 loops=1) Index Cond: (col1 > tbl1.col1) (15 rows) select tbl1.col1, tprt.col1 from tbl1 inner join tprt on tbl1.col1 < tprt.col1 order by tbl1.col1, tprt.col1; col1 | col1 ------+------ 4400 | 4500 (1 row) -- No matching partition delete from tbl1; insert into tbl1 values (10000); explain (analyze, costs off, summary off, timing off) select * from tbl1 join tprt on tbl1.col1 = tprt.col1; QUERY PLAN ------------------------------------------------------------------- Nested Loop (actual rows=0 loops=1) -> Seq Scan on tbl1 (actual rows=1 loops=1) -> Append (actual rows=0 loops=1) -> Index Scan using tprt1_idx on tprt_1 (never executed) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt2_idx on tprt_2 (never executed) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt3_idx on tprt_3 (never executed) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt4_idx on tprt_4 (never executed) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt5_idx on tprt_5 (never executed) Index Cond: (col1 = tbl1.col1) -> Index Scan using tprt6_idx on tprt_6 (never executed) Index Cond: (col1 = tbl1.col1) (15 rows) select tbl1.col1, tprt.col1 from tbl1 inner join tprt on tbl1.col1 = tprt.col1 order by tbl1.col1, tprt.col1; col1 | col1 ------+------ (0 rows) drop table tbl1, tprt; -- Test with columns defined in varying orders between each level create table part_abc (a int not null, b int not null, c int not null) partition by list (a); create table part_bac (b int not null, a int not null, c int not null) partition by list (b); create table part_cab (c int not null, a int not null, b int not null) partition by list (c); create table part_abc_p1 (a int not null, b int not null, c int not null); alter table part_abc attach partition part_bac for values in(1); alter table part_bac attach partition part_cab for values in(2); alter table part_cab attach partition part_abc_p1 for values in(3); prepare part_abc_q1 (int, int, int) as select * from part_abc where a = $1 and b = $2 and c = $3; -- Single partition should be scanned. explain (analyze, costs off, summary off, timing off) execute part_abc_q1 (1, 2, 3); QUERY PLAN ---------------------------------------------------------- Seq Scan on part_abc_p1 part_abc (actual rows=0 loops=1) Filter: ((a = $1) AND (b = $2) AND (c = $3)) (2 rows) deallocate part_abc_q1; drop table part_abc; -- Ensure that an Append node properly handles a sub-partitioned table -- matching without any of its leaf partitions matching the clause. create table listp (a int, b int) partition by list (a); create table listp_1 partition of listp for values in(1) partition by list (b); create table listp_1_1 partition of listp_1 for values in(1); create table listp_2 partition of listp for values in(2) partition by list (b); create table listp_2_1 partition of listp_2 for values in(2); select * from listp where b = 1; a | b ---+--- (0 rows) -- 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) as select * from listp where b in ($1,$2); explain (analyze, costs off, summary off, timing off) execute q1 (1,1); QUERY PLAN ------------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 1 -> Seq Scan on listp_1_1 listp_1 (actual rows=0 loops=1) Filter: (b = ANY (ARRAY[$1, $2])) (4 rows) explain (analyze, costs off, summary off, timing off) execute q1 (2,2); QUERY PLAN ------------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 1 -> Seq Scan on listp_2_1 listp_1 (actual rows=0 loops=1) Filter: (b = ANY (ARRAY[$1, $2])) (4 rows) -- Try with no matching partitions. explain (analyze, costs off, summary off, timing off) execute q1 (0,0); QUERY PLAN -------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 2 (2 rows) deallocate q1; -- Test more complex cases where a not-equal condition further eliminates partitions. prepare q1 (int,int,int,int) as select * 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) execute q1 (1,2,2,0); QUERY PLAN ------------------------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 1 -> Seq Scan on listp_1_1 listp_1 (actual rows=0 loops=1) Filter: ((b = ANY (ARRAY[$1, $2])) AND ($3 <> b) AND ($4 <> b)) (4 rows) -- Both partitions allowed by IN clause, then both excluded again by <> clauses. explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,1); QUERY PLAN -------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 2 (2 rows) -- Ensure Params that evaluate to NULL properly prune away all partitions explain (analyze, costs off, summary off, timing off) select * from listp where a = (select null::int); QUERY PLAN ------------------------------------------------------ Append (actual rows=0 loops=1) InitPlan 1 -> Result (actual rows=1 loops=1) -> Seq Scan on listp_1_1 listp_1 (never executed) Filter: (a = (InitPlan 1).col1) -> Seq Scan on listp_2_1 listp_2 (never executed) Filter: (a = (InitPlan 1).col1) (7 rows) drop table listp; -- -- check that stable query clauses are only used in run-time pruning -- create table stable_qual_pruning (a timestamp) partition by range (a); create table stable_qual_pruning1 partition of stable_qual_pruning for values from ('2000-01-01') to ('2000-02-01'); create table stable_qual_pruning2 partition of stable_qual_pruning for values from ('2000-02-01') to ('2000-03-01'); create table stable_qual_pruning3 partition of stable_qual_pruning for values from ('3000-02-01') to ('3000-03-01'); -- comparison against a stable value requires run-time pruning explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a < localtimestamp; QUERY PLAN -------------------------------------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 1 -> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (actual rows=0 loops=1) Filter: (a < LOCALTIMESTAMP) -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_2 (actual rows=0 loops=1) Filter: (a < LOCALTIMESTAMP) (6 rows) -- timestamp < timestamptz comparison is only stable, not immutable explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a < '2000-02-01'::timestamptz; QUERY PLAN -------------------------------------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 2 -> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (actual rows=0 loops=1) Filter: (a < 'Tue Feb 01 00:00:00 2000 PST'::timestamp with time zone) (4 rows) -- check ScalarArrayOp cases explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a = any(array['2010-02-01', '2020-01-01']::timestamp[]); QUERY PLAN -------------------------------- Result (actual rows=0 loops=1) One-Time Filter: false (2 rows) explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a = any(array['2000-02-01', '2010-01-01']::timestamp[]); QUERY PLAN ---------------------------------------------------------------------------------------------------------------- Seq Scan on stable_qual_pruning2 stable_qual_pruning (actual rows=0 loops=1) Filter: (a = ANY ('{"Tue Feb 01 00:00:00 2000","Fri Jan 01 00:00:00 2010"}'::timestamp without time zone[])) (2 rows) explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a = any(array['2000-02-01', localtimestamp]::timestamp[]); QUERY PLAN ------------------------------------------------------------------------------------------------------------ Append (actual rows=0 loops=1) Subplans Removed: 2 -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_1 (actual rows=0 loops=1) Filter: (a = ANY (ARRAY['Tue Feb 01 00:00:00 2000'::timestamp without time zone, LOCALTIMESTAMP])) (4 rows) explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a = any(array['2010-02-01', '2020-01-01']::timestamptz[]); QUERY PLAN -------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 3 (2 rows) explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a = any(array['2000-02-01', '2010-01-01']::timestamptz[]); QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Append (actual rows=0 loops=1) Subplans Removed: 2 -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_1 (actual rows=0 loops=1) Filter: (a = ANY ('{"Tue Feb 01 00:00:00 2000 PST","Fri Jan 01 00:00:00 2010 PST"}'::timestamp with time zone[])) (4 rows) explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a = any(null::timestamptz[]); QUERY PLAN -------------------------------------------------------------------------------------- Append (actual rows=0 loops=1) -> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (actual rows=0 loops=1) Filter: (a = ANY (NULL::timestamp with time zone[])) -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_2 (actual rows=0 loops=1) Filter: (a = ANY (NULL::timestamp with time zone[])) -> Seq Scan on stable_qual_pruning3 stable_qual_pruning_3 (actual rows=0 loops=1) Filter: (a = ANY (NULL::timestamp with time zone[])) (7 rows) drop table 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 -- create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); create table mc3p0 partition of mc3p for values from (0, 0, 0) to (0, maxvalue, maxvalue); create table mc3p1 partition of mc3p for values from (1, 1, 1) to (2, minvalue, minvalue); create table mc3p2 partition of mc3p for values from (2, minvalue, minvalue) to (3, maxvalue, maxvalue); insert into mc3p values (0, 1, 1), (1, 1, 1), (2, 1, 1); explain (analyze, costs off, summary off, timing off) select * from mc3p where a < 3 and abs(b) = 1; QUERY PLAN -------------------------------------------------------- Append (actual rows=3 loops=1) -> Seq Scan on mc3p0 mc3p_1 (actual rows=1 loops=1) Filter: ((a < 3) AND (abs(b) = 1)) -> Seq Scan on mc3p1 mc3p_2 (actual rows=1 loops=1) Filter: ((a < 3) AND (abs(b) = 1)) -> Seq Scan on mc3p2 mc3p_3 (actual rows=1 loops=1) Filter: ((a < 3) AND (abs(b) = 1)) (7 rows) -- -- 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 = $1 and abs(b) < (select 3); explain (analyze, costs off, summary off, timing off) execute ps1(1); QUERY PLAN ------------------------------------------------------------- Append (actual rows=1 loops=1) Subplans Removed: 2 InitPlan 1 -> Result (actual rows=1 loops=1) -> Seq Scan on mc3p1 mc3p_1 (actual rows=1 loops=1) Filter: ((a = $1) AND (abs(b) < (InitPlan 1).col1)) (6 rows) deallocate ps1; prepare ps2 as select * from mc3p where a <= $1 and abs(b) < (select 3); explain (analyze, costs off, summary off, timing off) execute ps2(1); QUERY PLAN -------------------------------------------------------------- Append (actual rows=2 loops=1) Subplans Removed: 1 InitPlan 1 -> Result (actual rows=1 loops=1) -> Seq Scan on mc3p0 mc3p_1 (actual rows=1 loops=1) Filter: ((a <= $1) AND (abs(b) < (InitPlan 1).col1)) -> Seq Scan on mc3p1 mc3p_2 (actual rows=1 loops=1) Filter: ((a <= $1) AND (abs(b) < (InitPlan 1).col1)) (8 rows) deallocate ps2; drop table mc3p; -- Ensure runtime pruning works with initplans params with boolean types create table boolvalues (value bool not null); insert into boolvalues values('t'),('f'); create table boolp (a bool) partition by list (a); create table boolp_t partition of boolp for values in('t'); create table boolp_f partition of boolp for values in('f'); explain (analyze, costs off, summary off, timing off) select * from boolp where a = (select value from boolvalues where value); QUERY PLAN ----------------------------------------------------------- Append (actual rows=0 loops=1) InitPlan 1 -> Seq Scan on boolvalues (actual rows=1 loops=1) Filter: value Rows Removed by Filter: 1 -> Seq Scan on boolp_f boolp_1 (never executed) Filter: (a = (InitPlan 1).col1) -> Seq Scan on boolp_t boolp_2 (actual rows=0 loops=1) Filter: (a = (InitPlan 1).col1) (9 rows) explain (analyze, costs off, summary off, timing off) select * from boolp where a = (select value from boolvalues where not value); QUERY PLAN ----------------------------------------------------------- Append (actual rows=0 loops=1) InitPlan 1 -> Seq Scan on boolvalues (actual rows=1 loops=1) Filter: (NOT value) Rows Removed by Filter: 1 -> Seq Scan on boolp_f boolp_1 (actual rows=0 loops=1) Filter: (a = (InitPlan 1).col1) -> Seq Scan on boolp_t boolp_2 (never executed) Filter: (a = (InitPlan 1).col1) (9 rows) drop table boolp; -- -- Test run-time pruning of MergeAppend subnodes -- set enable_seqscan = off; set enable_sort = off; create table ma_test (a int, b int) partition by range (a); create table ma_test_p1 partition of ma_test for values from (0) to (10); create table ma_test_p2 partition of ma_test for values from (10) to (20); create table ma_test_p3 partition of ma_test for values from (20) to (30); insert into ma_test select x,x from generate_series(0,29) t(x); create index on ma_test (b); analyze ma_test; prepare mt_q1 (int) as select a from ma_test where a >= $1 and a % 10 = 5 order by b; explain (analyze, costs off, summary off, timing off) execute mt_q1(15); QUERY PLAN ----------------------------------------------------------------------------------------- Merge Append (actual rows=2 loops=1) Sort Key: ma_test.b Subplans Removed: 1 -> Index Scan using ma_test_p2_b_idx on ma_test_p2 ma_test_1 (actual rows=1 loops=1) Filter: ((a >= $1) AND ((a % 10) = 5)) Rows Removed by Filter: 9 -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_2 (actual rows=1 loops=1) Filter: ((a >= $1) AND ((a % 10) = 5)) Rows Removed by Filter: 9 (9 rows) execute mt_q1(15); a ---- 15 25 (2 rows) explain (analyze, costs off, summary off, timing off) execute mt_q1(25); QUERY PLAN ----------------------------------------------------------------------------------------- Merge Append (actual rows=1 loops=1) Sort Key: ma_test.b Subplans Removed: 2 -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_1 (actual rows=1 loops=1) Filter: ((a >= $1) AND ((a % 10) = 5)) Rows Removed by Filter: 9 (6 rows) execute mt_q1(25); a ---- 25 (1 row) -- Ensure MergeAppend behaves correctly when no subplans match explain (analyze, costs off, summary off, timing off) execute mt_q1(35); QUERY PLAN -------------------------------------- Merge Append (actual rows=0 loops=1) Sort Key: ma_test.b Subplans Removed: 3 (3 rows) execute mt_q1(35); a --- (0 rows) deallocate mt_q1; prepare mt_q2 (int) as select * from ma_test where a >= $1 order by b limit 1; -- Ensure output list looks sane when the MergeAppend has no subplans. explain (analyze, verbose, costs off, summary off, timing off) execute mt_q2 (35); QUERY PLAN -------------------------------------------- Limit (actual rows=0 loops=1) Output: ma_test.a, ma_test.b -> Merge Append (actual rows=0 loops=1) Sort Key: ma_test.b Subplans Removed: 3 (5 rows) deallocate mt_q2; -- ensure initplan params properly prune partitions explain (analyze, costs off, summary off, timing off) select * from ma_test where a >= (select min(b) from ma_test_p2) order by b; QUERY PLAN ----------------------------------------------------------------------------------------------- Merge Append (actual rows=20 loops=1) Sort Key: ma_test.b InitPlan 2 -> Result (actual rows=1 loops=1) InitPlan 1 -> Limit (actual rows=1 loops=1) -> Index Scan using ma_test_p2_b_idx on ma_test_p2 (actual rows=1 loops=1) Index Cond: (b IS NOT NULL) -> Index Scan using ma_test_p1_b_idx on ma_test_p1 ma_test_1 (never executed) Filter: (a >= (InitPlan 2).col1) -> Index Scan using ma_test_p2_b_idx on ma_test_p2 ma_test_2 (actual rows=10 loops=1) Filter: (a >= (InitPlan 2).col1) -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_3 (actual rows=10 loops=1) Filter: (a >= (InitPlan 2).col1) (14 rows) reset enable_seqscan; reset enable_sort; drop table ma_test; reset enable_indexonlyscan; -- -- check that pruning works properly when the partition key is of a -- pseudotype -- -- array type list partition key create table pp_arrpart (a int[]) partition by list (a); create table pp_arrpart1 partition of pp_arrpart for values in ('{1}'); create table pp_arrpart2 partition of pp_arrpart for values in ('{2, 3}', '{4, 5}'); explain (costs off) select * from pp_arrpart where a = '{1}'; QUERY PLAN ------------------------------------ Seq Scan on pp_arrpart1 pp_arrpart Filter: (a = '{1}'::integer[]) (2 rows) explain (costs off) select * from pp_arrpart where a = '{1, 2}'; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) explain (costs off) select * from pp_arrpart where a in ('{4, 5}', '{1}'); QUERY PLAN ---------------------------------------------------------------------- Append -> Seq Scan on pp_arrpart1 pp_arrpart_1 Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) -> Seq Scan on pp_arrpart2 pp_arrpart_2 Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) (5 rows) explain (costs off) update pp_arrpart set a = a where a = '{1}'; QUERY PLAN -------------------------------------------- Update on pp_arrpart Update on pp_arrpart1 pp_arrpart_1 -> Seq Scan on pp_arrpart1 pp_arrpart_1 Filter: (a = '{1}'::integer[]) (4 rows) explain (costs off) delete from pp_arrpart where a = '{1}'; QUERY PLAN -------------------------------------------- Delete on pp_arrpart Delete on pp_arrpart1 pp_arrpart_1 -> Seq Scan on pp_arrpart1 pp_arrpart_1 Filter: (a = '{1}'::integer[]) (4 rows) drop table pp_arrpart; -- array type hash partition key create table pph_arrpart (a int[]) partition by hash (a); create table pph_arrpart1 partition of pph_arrpart for values with (modulus 2, remainder 0); create table pph_arrpart2 partition of pph_arrpart for values with (modulus 2, remainder 1); insert into pph_arrpart values ('{1}'), ('{1, 2}'), ('{4, 5}'); select tableoid::regclass, * from pph_arrpart order by 1; tableoid | a --------------+------- pph_arrpart1 | {1,2} pph_arrpart1 | {4,5} pph_arrpart2 | {1} (3 rows) explain (costs off) select * from pph_arrpart where a = '{1}'; QUERY PLAN -------------------------------------- Seq Scan on pph_arrpart2 pph_arrpart Filter: (a = '{1}'::integer[]) (2 rows) explain (costs off) select * from pph_arrpart where a = '{1, 2}'; QUERY PLAN -------------------------------------- Seq Scan on pph_arrpart1 pph_arrpart Filter: (a = '{1,2}'::integer[]) (2 rows) explain (costs off) select * from pph_arrpart where a in ('{4, 5}', '{1}'); QUERY PLAN ---------------------------------------------------------------------- Append -> Seq Scan on pph_arrpart1 pph_arrpart_1 Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) -> Seq Scan on pph_arrpart2 pph_arrpart_2 Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) (5 rows) drop table pph_arrpart; -- enum type list partition key create type pp_colors as enum ('green', 'blue', 'black'); create table pp_enumpart (a pp_colors) partition by list (a); create table pp_enumpart_green partition of pp_enumpart for values in ('green'); create table pp_enumpart_blue partition of pp_enumpart for values in ('blue'); explain (costs off) select * from pp_enumpart where a = 'blue'; QUERY PLAN ------------------------------------------ Seq Scan on pp_enumpart_blue pp_enumpart Filter: (a = 'blue'::pp_colors) (2 rows) explain (costs off) select * from pp_enumpart where a = 'black'; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) drop table pp_enumpart; drop type pp_colors; -- record type as partition key create type pp_rectype as (a int, b int); create table pp_recpart (a pp_rectype) partition by list (a); create table pp_recpart_11 partition of pp_recpart for values in ('(1,1)'); create table pp_recpart_23 partition of pp_recpart for values in ('(2,3)'); explain (costs off) select * from pp_recpart where a = '(1,1)'::pp_rectype; QUERY PLAN -------------------------------------- Seq Scan on pp_recpart_11 pp_recpart Filter: (a = '(1,1)'::pp_rectype) (2 rows) explain (costs off) select * from pp_recpart where a = '(1,2)'::pp_rectype; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) drop table pp_recpart; drop type pp_rectype; -- range type partition key create table pp_intrangepart (a int4range) partition by list (a); create table pp_intrangepart12 partition of pp_intrangepart for values in ('[1,2]'); create table pp_intrangepart2inf partition of pp_intrangepart for values in ('[2,)'); explain (costs off) select * from pp_intrangepart where a = '[1,2]'::int4range; QUERY PLAN ----------------------------------------------- Seq Scan on pp_intrangepart12 pp_intrangepart Filter: (a = '[1,3)'::int4range) (2 rows) explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) drop table pp_intrangepart; -- -- Ensure the enable_partition_prune GUC properly disables partition pruning. -- create table pp_lp (a int, value int) partition by list (a); create table pp_lp1 partition of pp_lp for values in(1); create table pp_lp2 partition of pp_lp for values in(2); explain (costs off) select * from pp_lp where a = 1; QUERY PLAN -------------------------- Seq Scan on pp_lp1 pp_lp Filter: (a = 1) (2 rows) explain (costs off) update pp_lp set value = 10 where a = 1; QUERY PLAN ---------------------------------- Update on pp_lp Update on pp_lp1 pp_lp_1 -> Seq Scan on pp_lp1 pp_lp_1 Filter: (a = 1) (4 rows) explain (costs off) delete from pp_lp where a = 1; QUERY PLAN ---------------------------------- Delete on pp_lp Delete on pp_lp1 pp_lp_1 -> Seq Scan on pp_lp1 pp_lp_1 Filter: (a = 1) (4 rows) 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; QUERY PLAN ---------------------------------- Append -> Seq Scan on pp_lp1 pp_lp_1 Filter: (a = 1) -> Seq Scan on pp_lp2 pp_lp_2 Filter: (a = 1) (5 rows) explain (costs off) update pp_lp set value = 10 where a = 1; QUERY PLAN ---------------------------------------- Update on pp_lp Update on pp_lp1 pp_lp_1 Update on pp_lp2 pp_lp_2 -> Append -> Seq Scan on pp_lp1 pp_lp_1 Filter: (a = 1) -> Seq Scan on pp_lp2 pp_lp_2 Filter: (a = 1) (8 rows) explain (costs off) delete from pp_lp where a = 1; QUERY PLAN ---------------------------------------- Delete on pp_lp Delete on pp_lp1 pp_lp_1 Delete on pp_lp2 pp_lp_2 -> Append -> Seq Scan on pp_lp1 pp_lp_1 Filter: (a = 1) -> Seq Scan on pp_lp2 pp_lp_2 Filter: (a = 1) (8 rows) set constraint_exclusion = 'off'; -- this should not affect the result. explain (costs off) select * from pp_lp where a = 1; QUERY PLAN ---------------------------------- Append -> Seq Scan on pp_lp1 pp_lp_1 Filter: (a = 1) -> Seq Scan on pp_lp2 pp_lp_2 Filter: (a = 1) (5 rows) explain (costs off) update pp_lp set value = 10 where a = 1; QUERY PLAN ---------------------------------------- Update on pp_lp Update on pp_lp1 pp_lp_1 Update on pp_lp2 pp_lp_2 -> Append -> Seq Scan on pp_lp1 pp_lp_1 Filter: (a = 1) -> Seq Scan on pp_lp2 pp_lp_2 Filter: (a = 1) (8 rows) explain (costs off) delete from pp_lp where a = 1; QUERY PLAN ---------------------------------------- Delete on pp_lp Delete on pp_lp1 pp_lp_1 Delete on pp_lp2 pp_lp_2 -> Append -> Seq Scan on pp_lp1 pp_lp_1 Filter: (a = 1) -> Seq Scan on pp_lp2 pp_lp_2 Filter: (a = 1) (8 rows) drop table pp_lp; -- Ensure enable_partition_prune does not affect non-partitioned tables. create table inh_lp (a int, value int); create table inh_lp1 (a int, value int, check(a = 1)) inherits (inh_lp); NOTICE: merging column "a" with inherited definition NOTICE: merging column "value" with inherited definition create table inh_lp2 (a int, value int, check(a = 2)) inherits (inh_lp); NOTICE: merging column "a" with inherited definition NOTICE: merging column "value" with inherited definition set constraint_exclusion = 'partition'; -- inh_lp2 should be removed in the following 3 cases. explain (costs off) select * from inh_lp where a = 1; QUERY PLAN ------------------------------------ Append -> Seq Scan on inh_lp inh_lp_1 Filter: (a = 1) -> Seq Scan on inh_lp1 inh_lp_2 Filter: (a = 1) (5 rows) explain (costs off) update inh_lp set value = 10 where a = 1; QUERY PLAN ------------------------------------------------ Update on inh_lp Update on inh_lp inh_lp_1 Update on inh_lp1 inh_lp_2 -> Result -> Append -> Seq Scan on inh_lp inh_lp_1 Filter: (a = 1) -> Seq Scan on inh_lp1 inh_lp_2 Filter: (a = 1) (9 rows) explain (costs off) delete from inh_lp where a = 1; QUERY PLAN ------------------------------------------ Delete on inh_lp Delete on inh_lp inh_lp_1 Delete on inh_lp1 inh_lp_2 -> Append -> Seq Scan on inh_lp inh_lp_1 Filter: (a = 1) -> Seq Scan on inh_lp1 inh_lp_2 Filter: (a = 1) (8 rows) -- Ensure we don't exclude normal relations when we only expect to exclude -- inheritance children explain (costs off) update inh_lp1 set value = 10 where a = 2; QUERY PLAN --------------------------- Update on inh_lp1 -> Seq Scan on inh_lp1 Filter: (a = 2) (3 rows) drop table inh_lp cascade; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to table inh_lp1 drop cascades to table inh_lp2 reset enable_partition_pruning; reset constraint_exclusion; -- 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 for values in (1); create temp table pp_temp_part_def partition of pp_temp_parent default; explain (costs off) select * from pp_temp_parent where true; QUERY PLAN ----------------------------------------------------- Append -> Seq Scan on pp_temp_part_1 pp_temp_parent_1 -> Seq Scan on pp_temp_part_def pp_temp_parent_2 (3 rows) explain (costs off) select * from pp_temp_parent where a = 2; QUERY PLAN --------------------------------------------- Seq Scan on pp_temp_part_def pp_temp_parent Filter: (a = 2) (2 rows) drop table 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 for values in (1); create temp table p2 partition of p for values in (2); create temp table q (a int, b int, c int) partition by list (a); create temp table q1 partition of q for values in (1) partition by list (b); create temp table q11 partition of q1 for values in (1) partition by list (c); create temp table q111 partition of q11 for values in (1); create temp table q2 partition of q for values in (2) partition by list (b); create temp table q21 partition of q2 for values in (1); create temp table q22 partition of q2 for values in (2); insert into q22 values (2, 2, 3); explain (costs off) select * from ( select * from p union all select * from q1 union all select 1, 1, 1 ) s(a, b, c) where s.a = 1 and s.b = 1 and s.c = (select 1); QUERY PLAN ------------------------------------------------------------------- Append InitPlan 1 -> Result -> Seq Scan on p1 p Filter: ((a = 1) AND (b = 1) AND (c = (InitPlan 1).col1)) -> Seq Scan on q111 q1 Filter: ((a = 1) AND (b = 1) AND (c = (InitPlan 1).col1)) -> Result One-Time Filter: (1 = (InitPlan 1).col1) (9 rows) select * from ( select * from p union all select * from q1 union all select 1, 1, 1 ) s(a, b, c) where s.a = 1 and s.b = 1 and s.c = (select 1); a | b | c ---+---+--- 1 | 1 | 1 (1 row) prepare q (int, int) as select * from ( select * from p union all select * from q1 union all select 1, 1, 1 ) s(a, b, c) where s.a = $1 and s.b = $2 and s.c = (select 1); explain (costs off) execute q (1, 1); QUERY PLAN ------------------------------------------------------------------------------ Append Subplans Removed: 1 InitPlan 1 -> Result -> Seq Scan on p1 p Filter: ((a = $1) AND (b = $2) AND (c = (InitPlan 1).col1)) -> Seq Scan on q111 q1 Filter: ((a = $1) AND (b = $2) AND (c = (InitPlan 1).col1)) -> Result One-Time Filter: ((1 = $1) AND (1 = $2) AND (1 = (InitPlan 1).col1)) (10 rows) execute q (1, 1); a | b | c ---+---+--- 1 | 1 | 1 (1 row) drop table p, q; -- 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. create table listp (a int, b int) partition by list (a); create table listp1 partition of listp for values in(1); create table listp2 partition of listp for values in(2) partition by list(b); create table listp2_10 partition of listp2 for values in (10); explain (analyze, costs off, summary off, timing off) select * from listp where a = (select 2) and b <> 10; QUERY PLAN --------------------------------------------------- Seq Scan on listp1 listp (actual rows=0 loops=1) Filter: ((b <> 10) AND (a = (InitPlan 1).col1)) InitPlan 1 -> Result (never executed) (4 rows) -- -- 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; QUERY PLAN -------------------- Seq Scan on listp1 Filter: (a = 2) (2 rows) explain (costs off) update listp1 set a = 1 where a = 2; QUERY PLAN -------------------------- Update on listp1 -> Seq Scan on listp1 Filter: (a = 2) (3 rows) -- constraint exclusion enabled set constraint_exclusion to 'on'; explain (costs off) select * from listp1 where a = 2; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) explain (costs off) update listp1 set a = 1 where a = 2; QUERY PLAN -------------------------------- Update on listp1 -> Result One-Time Filter: false (3 rows) reset constraint_exclusion; reset enable_partition_pruning; drop table listp; -- Ensure run-time pruning works correctly for nested Append nodes set parallel_setup_cost to 0; set parallel_tuple_cost to 0; create table listp (a int) partition by list(a); create table listp_12 partition of listp for values in(1,2) partition by list(a); create table listp_12_1 partition of listp_12 for values in(1); create table listp_12_2 partition of listp_12 for values in(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. alter table 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);'); explain_parallel_append ---------------------------------------------------------------------- Gather (actual rows=N loops=N) Workers Planned: 2 Workers Launched: N InitPlan 1 -> Result (actual rows=N loops=N) -> Parallel Append (actual rows=N loops=N) -> Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N) Filter: (a = (InitPlan 1).col1) -> Parallel Seq Scan on listp_12_2 listp_2 (never executed) Filter: (a = (InitPlan 1).col1) (10 rows) -- 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) union all select * from listp where a = (select 2);'); explain_parallel_append ----------------------------------------------------------------------------------- Gather (actual rows=N loops=N) Workers Planned: 2 Workers Launched: N -> Parallel Append (actual rows=N loops=N) -> Parallel Append (actual rows=N loops=N) InitPlan 2 -> Result (actual rows=N loops=N) -> Seq Scan on listp_12_1 listp_1 (never executed) Filter: (a = (InitPlan 2).col1) -> Parallel Seq Scan on listp_12_2 listp_2 (actual rows=N loops=N) Filter: (a = (InitPlan 2).col1) -> Parallel Append (actual rows=N loops=N) InitPlan 1 -> Result (actual rows=N loops=N) -> Seq Scan on listp_12_1 listp_4 (actual rows=N loops=N) Filter: (a = (InitPlan 1).col1) -> Parallel Seq Scan on listp_12_2 listp_5 (never executed) Filter: (a = (InitPlan 1).col1) (18 rows) drop table listp; reset parallel_tuple_cost; reset parallel_setup_cost; -- Test case for run-time pruning with a nested Merge Append set enable_sort to 0; create table rangep (a int, b int) partition by range (a); create table rangep_0_to_100 partition of rangep for values from (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. create table rangep_0_to_100_1 partition of rangep_0_to_100 for values in(1); create table rangep_0_to_100_2 partition of rangep_0_to_100 for values in(2); create table rangep_0_to_100_3 partition of rangep_0_to_100 for values in(3); create table rangep_100_to_200 partition of rangep for values from (100) to (200); create index on rangep (a); -- Ensure run-time pruning works on the nested Merge Append explain (analyze on, costs off, timing off, summary off) select * from rangep where b IN((select 1),(select 2)) order by a; QUERY PLAN ------------------------------------------------------------------------------------------------------------ Append (actual rows=0 loops=1) InitPlan 1 -> Result (actual rows=1 loops=1) InitPlan 2 -> Result (actual rows=1 loops=1) -> Merge Append (actual rows=0 loops=1) Sort Key: rangep_2.a -> Index Scan using rangep_0_to_100_1_a_idx on rangep_0_to_100_1 rangep_2 (actual rows=0 loops=1) Filter: (b = ANY (ARRAY[(InitPlan 1).col1, (InitPlan 2).col1])) -> Index Scan using rangep_0_to_100_2_a_idx on rangep_0_to_100_2 rangep_3 (actual rows=0 loops=1) Filter: (b = ANY (ARRAY[(InitPlan 1).col1, (InitPlan 2).col1])) -> Index Scan using rangep_0_to_100_3_a_idx on rangep_0_to_100_3 rangep_4 (never executed) Filter: (b = ANY (ARRAY[(InitPlan 1).col1, (InitPlan 2).col1])) -> Index Scan using rangep_100_to_200_a_idx on rangep_100_to_200 rangep_5 (actual rows=0 loops=1) Filter: (b = ANY (ARRAY[(InitPlan 1).col1, (InitPlan 2).col1])) (15 rows) reset enable_sort; drop table rangep; -- -- Check that gen_prune_steps_from_opexps() works well for various cases of -- clauses for different partition keys -- create table rp_prefix_test1 (a int, b varchar) partition by range(a, b); create table rp_prefix_test1_p1 partition of rp_prefix_test1 for values from (1, 'a') to (1, 'b'); create table rp_prefix_test1_p2 partition of rp_prefix_test1 for values from (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 <= 1 and b = 'a'; QUERY PLAN -------------------------------------------------- Seq Scan on rp_prefix_test1_p1 rp_prefix_test1 Filter: ((a <= 1) AND ((b)::text = 'a'::text)) (2 rows) create table rp_prefix_test2 (a int, b int, c int) partition by range(a, b, c); create table rp_prefix_test2_p1 partition of rp_prefix_test2 for values from (1, 1, 0) to (1, 1, 10); create table rp_prefix_test2_p2 partition of rp_prefix_test2 for values from (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 <= 1 and b = 1 and c >= 0; QUERY PLAN ------------------------------------------------ Seq Scan on rp_prefix_test2_p1 rp_prefix_test2 Filter: ((a <= 1) AND (c >= 0) AND (b = 1)) (2 rows) create table rp_prefix_test3 (a int, b int, c int, d int) partition by range(a, b, c, d); create table rp_prefix_test3_p1 partition of rp_prefix_test3 for values from (1, 1, 1, 0) to (1, 1, 1, 10); create table rp_prefix_test3_p2 partition of rp_prefix_test3 for values from (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 >= 1 and b >= 1 and b >= 2 and c >= 2 and d >= 0; QUERY PLAN -------------------------------------------------------------------------- Seq Scan on rp_prefix_test3_p2 rp_prefix_test3 Filter: ((a >= 1) AND (b >= 1) AND (b >= 2) AND (c >= 2) AND (d >= 0)) (2 rows) -- 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 >= 1 and b >= 1 and b = 2 and c = 2 and d >= 0; QUERY PLAN ------------------------------------------------------------------------ Seq Scan on rp_prefix_test3_p2 rp_prefix_test3 Filter: ((a >= 1) AND (b >= 1) AND (d >= 0) AND (b = 2) AND (c = 2)) (2 rows) drop table rp_prefix_test1; drop table rp_prefix_test2; drop table rp_prefix_test3; -- -- Test that get_steps_using_prefix() handles IS NULL clauses correctly -- create table 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; ?column? ------------------------------------------------------------------------------------------------------ create table hp_prefix_test_p0 partition of hp_prefix_test for values with (modulus 8, remainder 0); create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 8, remainder 1); create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 8, remainder 2); create table hp_prefix_test_p3 partition of hp_prefix_test for values with (modulus 8, remainder 3); create table hp_prefix_test_p4 partition of hp_prefix_test for values with (modulus 8, remainder 4); create table hp_prefix_test_p5 partition of hp_prefix_test for values with (modulus 8, remainder 5); create table hp_prefix_test_p6 partition of hp_prefix_test for values with (modulus 8, remainder 6); create table hp_prefix_test_p7 partition of hp_prefix_test for values with (modulus 8, remainder 7); (8 rows) \gexec create table hp_prefix_test_p0 partition of hp_prefix_test for values with (modulus 8, remainder 0); create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 8, remainder 1); create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 8, remainder 2); create table hp_prefix_test_p3 partition of hp_prefix_test for values with (modulus 8, remainder 3); create table hp_prefix_test_p4 partition of hp_prefix_test for values with (modulus 8, remainder 4); create table hp_prefix_test_p5 partition of hp_prefix_test for values with (modulus 8, remainder 5); create table hp_prefix_test_p6 partition of hp_prefix_test for values with (modulus 8, remainder 6); create table hp_prefix_test_p7 partition of hp_prefix_test for values with (modulus 8, remainder 7); -- insert 16 rows, one row for each test to perform. insert into hp_prefix_test select case a when 0 then null else 1 end, case b when 0 then null else 2 end, case c when 0 then null else 3 end, case d when 0 then null else 4 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 || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos) from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s) group by g.s order by g.s; explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 \gexec explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null Seq Scan on hp_prefix_test_p0 hp_prefix_test Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d IS NULL)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null Seq Scan on hp_prefix_test_p1 hp_prefix_test Filter: ((b IS NULL) AND (c IS NULL) AND (d IS NULL) AND (a = 1)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null Seq Scan on hp_prefix_test_p2 hp_prefix_test Filter: ((a IS NULL) AND (c IS NULL) AND (d IS NULL) AND (b = 2)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null Seq Scan on hp_prefix_test_p4 hp_prefix_test Filter: ((c IS NULL) AND (d IS NULL) AND (a = 1) AND (b = 2)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null Seq Scan on hp_prefix_test_p3 hp_prefix_test Filter: ((a IS NULL) AND (b IS NULL) AND (d IS NULL) AND (c = 3)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null Seq Scan on hp_prefix_test_p7 hp_prefix_test Filter: ((b IS NULL) AND (d IS NULL) AND (a = 1) AND (c = 3)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null Seq Scan on hp_prefix_test_p4 hp_prefix_test Filter: ((a IS NULL) AND (d IS NULL) AND (b = 2) AND (c = 3)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null Seq Scan on hp_prefix_test_p5 hp_prefix_test Filter: ((d IS NULL) AND (a = 1) AND (b = 2) AND (c = 3)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 Seq Scan on hp_prefix_test_p4 hp_prefix_test Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d = 4)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 Seq Scan on hp_prefix_test_p6 hp_prefix_test Filter: ((b IS NULL) AND (c IS NULL) AND (a = 1) AND (d = 4)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 Seq Scan on hp_prefix_test_p5 hp_prefix_test Filter: ((a IS NULL) AND (c IS NULL) AND (b = 2) AND (d = 4)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 Seq Scan on hp_prefix_test_p6 hp_prefix_test Filter: ((c IS NULL) AND (a = 1) AND (b = 2) AND (d = 4)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 Seq Scan on hp_prefix_test_p4 hp_prefix_test Filter: ((a IS NULL) AND (b IS NULL) AND (c = 3) AND (d = 4)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 Seq Scan on hp_prefix_test_p5 hp_prefix_test Filter: ((b IS NULL) AND (a = 1) AND (c = 3) AND (d = 4)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 Seq Scan on hp_prefix_test_p6 hp_prefix_test Filter: ((a IS NULL) AND (b = 2) AND (c = 3) AND (d = 4)) explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 Seq Scan on hp_prefix_test_p4 hp_prefix_test Filter: ((a = 1) AND (b = 2) AND (c = 3) AND (d = 4)) -- 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 || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos) from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s) group by g.s order by g.s; select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 \gexec select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null hp_prefix_test_p0 | | | | select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null hp_prefix_test_p1 | 1 | | | select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null hp_prefix_test_p2 | | 2 | | select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null hp_prefix_test_p4 | 1 | 2 | | select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null hp_prefix_test_p3 | | | 3 | select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null hp_prefix_test_p7 | 1 | | 3 | select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null hp_prefix_test_p4 | | 2 | 3 | select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null hp_prefix_test_p5 | 1 | 2 | 3 | select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 hp_prefix_test_p4 | | | | 4 select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 hp_prefix_test_p6 | 1 | | | 4 select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 hp_prefix_test_p5 | | 2 | | 4 select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 hp_prefix_test_p6 | 1 | 2 | | 4 select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 hp_prefix_test_p4 | | | 3 | 4 select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 hp_prefix_test_p5 | 1 | | 3 | 4 select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 hp_prefix_test_p6 | | 2 | 3 | 4 select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 hp_prefix_test_p4 | 1 | 2 | 3 | 4 \t off drop table 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); create table hp_contradict_test (a int, b int) partition by hash (a part_test_int4_ops2, b part_test_int4_ops2); create table hp_contradict_test_p1 partition of hp_contradict_test for values with (modulus 2, remainder 0); create table hp_contradict_test_p2 partition of hp_contradict_test for values with (modulus 2, remainder 1); explain (costs off) select * from hp_contradict_test where a is null and a === 1 and b === 1; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 and a is null; QUERY PLAN -------------------------- Result One-Time Filter: false (2 rows) drop table hp_contradict_test; drop operator class part_test_int4_ops2 using hash; drop operator ===(int4, int4);