blob: 5a810e73f404a3751f816889014a369e1f8f4961 [file] [log] [blame]
-- Tests for Dynamic Partition Elimination, or partition pruning in
-- PostgreSQL terms, based on join quals.
-- This test requires autovacuum to be disabled to guarantee a consistent state
-- after vacuum. An inopportune autovacuum could cause an explicit vacuum to
-- skip. That leads to stale relallvisible stats which may prevent picking index
-- only scan plan shapes.
-- start_matchsubs
-- m/Memory Usage: \d+\w?B/
-- s/Memory Usage: \d+\w?B/Memory Usage: ###B/
-- m/Memory: \d+kB/
-- s/Memory: \d+kB/Memory: ###kB/
-- m/Buckets: \d+/
-- s/Buckets: \d+/Buckets: ###/
-- m/Batches: \d+/
-- s/Batches: \d+/Batches: ###/
-- m/Extra Text: \(seg\d+\)/
-- s/Extra Text: \(seg\d+\)/Extra Text: ###/
-- m/Hash chain length \d+\.\d+ avg, \d+ max/
-- s/Hash chain length \d+\.\d+ avg, \d+ max/Hash chain length ###/
-- m/using \d+ of \d+ buckets/
-- s/using \d+ of \d+ buckets/using ## of ### buckets/
-- end_matchsubs
drop schema if exists dpe_single cascade;
create schema dpe_single;
set search_path='dpe_single';
set gp_segments_for_planner=2;
set optimizer_segments=2;
set enable_incremental_sort=on;
drop table if exists pt;
drop table if exists pt1;
drop table if exists t;
drop table if exists t1;
create table pt(dist int, pt1 text, pt2 text, pt3 text, ptid int)
DISTRIBUTED BY (dist)
PARTITION BY RANGE(ptid)
(
START (0) END (5) EVERY (1),
DEFAULT PARTITION junk_data
)
;
-- pt1 table is originally created distributed randomly
-- But a random policy impacts data distribution which
-- might lead to unstable stats info. Some test cases
-- test plan thus become flaky. We avoid flakiness by
-- creating the table distributed hashly and after
-- loading all the data, changing policy to randomly without
-- data movement. Thus every time we will have a static
-- data distribution plus randomly policy.
create table pt1(dist int, pt1 text, pt2 text, pt3 text, ptid int)
DISTRIBUTED BY (dist)
PARTITION BY RANGE(ptid)
(
START (0) END (5) EVERY (1),
DEFAULT PARTITION junk_data
)
;
create table t(dist int, tid int, t1 text, t2 text);
create index pt1_idx on pt using btree (pt1);
create index ptid_idx on pt using btree (ptid);
create index ptid_pt1_idx on pt using btree (ptid, pt1);
insert into pt select i, 'hello' || i, 'world', 'drop this', i % 6 from generate_series(0,53) i;
insert into t select i, i % 6, 'hello' || i, 'bar' from generate_series(0,1) i;
create table t1(dist int, tid int, t1 text, t2 text);
insert into t1 select i, i % 6, 'hello' || i, 'bar' from generate_series(1,2) i;
insert into pt1 select * from pt;
insert into pt1 select dist, pt1, pt2, pt3, ptid-100 from pt;
alter table pt1 set with(REORGANIZE=false) DISTRIBUTED RANDOMLY;
vacuum analyze pt;
analyze pt1;
analyze t;
analyze t1;
--
-- Simple positive cases
--
explain (costs off, timing off, summary off, analyze) select * from t, pt where tid = ptid;
select * from t, pt where tid = ptid;
explain (costs off, timing off, summary off, analyze) select * from t, pt where tid + 1 = ptid;
select * from t, pt where tid + 1 = ptid;
explain (costs off, timing off, summary off, analyze) select * from t, pt where tid = ptid and t1 = 'hello' || tid;
select * from t, pt where tid = ptid and t1 = 'hello' || tid;
explain (costs off, timing off, summary off, analyze) select * from t, pt where t1 = pt1 and ptid = tid;
select * from t, pt where t1 = pt1 and ptid = tid;
--
-- in and exists clauses
--
explain (costs off, timing off, summary off, analyze) select * from pt where ptid in (select tid from t where t1 = 'hello' || tid);
select * from pt where ptid in (select tid from t where t1 = 'hello' || tid);
explain (costs off, timing off, summary off, analyze) select ptid from pt where ptid in (select tid from t where t1 = 'hello' || tid) and pt1 = 'hello1';
select ptid from pt where ptid in (select tid from t where t1 = 'hello' || tid) and pt1 = 'hello1';
-- start_ignore
-- Known_opt_diff: MPP-21320
-- end_ignore
-- Disable 'CXformSelect2DynamicIndexGet' to avoid picking Dynamic Index Scan and use this test
-- to showcase dpe alternative
select disable_xform('CXformSelect2DynamicIndexGet');
explain (costs off, timing off, summary off, analyze) select * from pt where exists (select 1 from t where tid = ptid and t1 = 'hello' || tid);
select * from pt where exists (select 1 from t where tid = ptid and t1 = 'hello' || tid);
-- enable xform
select enable_xform('CXformSelect2DynamicIndexGet');
--
-- group-by on top
--
explain (costs off, timing off, summary off, analyze) select count(*) from t, pt where tid = ptid;
select count(*) from t, pt where tid = ptid;
--
-- window function on top
--
explain (costs off, timing off, summary off, analyze) select *, rank() over (order by ptid,pt1) from t, pt where tid = ptid;
select *, rank() over (order by ptid,pt1) from t, pt where tid = ptid;
--
-- set ops
--
explain (costs off, timing off, summary off, analyze) select * from t, pt where tid = ptid
union all
select * from t, pt where tid + 2 = ptid;
select * from t, pt where tid = ptid
union all
select * from t, pt where tid + 2 = ptid;
--
-- set-ops
--
explain (costs off, timing off, summary off, analyze) select count(*) from
( select * from t, pt where tid = ptid
union all
select * from t, pt where tid + 2 = ptid
) foo;
select count(*) from
( select * from t, pt where tid = ptid
union all
select * from t, pt where tid + 2 = ptid
) foo;
--
-- other join types (NL)
--
set enable_hashjoin=off;
set enable_nestloop=on;
set enable_mergejoin=off;
explain (costs off, timing off, summary off, analyze) select * from t, pt where tid = ptid;
select * from t, pt where tid = ptid;
--
-- index scan
--
set enable_nestloop=on;
set enable_seqscan=off;
set enable_indexscan=on;
set enable_bitmapscan=off;
set enable_hashjoin=off;
-- start_ignore
-- Known_opt_diff: MPP-21322
-- end_ignore
explain (costs off, timing off, summary off, analyze) select * from t, pt where tid = ptid and pt1 = 'hello0';
select * from t, pt where tid = ptid and pt1 = 'hello0';
--
-- NL Index Scan
--
set enable_nestloop=on;
set enable_indexscan=on;
set enable_seqscan=off;
set enable_hashjoin=off;
explain (costs off, timing off, summary off, analyze) select * from t, pt where tid = ptid;
select * from t, pt where tid = ptid;
--
-- Negative test cases where transform does not apply
--
set enable_indexscan=off;
set enable_seqscan=on;
set enable_hashjoin=on;
set enable_nestloop=off;
explain (costs off, timing off, summary off, analyze) select * from t, pt where t1 = pt1;
select * from t, pt where t1 = pt1;
explain (costs off, timing off, summary off, analyze) select * from t, pt where tid < ptid;
select * from t, pt where tid < ptid;
reset enable_indexscan;
reset enable_seqscan;
reset enable_hashjoin;
reset enable_nestloop;
--
-- multiple joins
--
-- one of the joined tables can be used for partition elimination, the other can not
explain (costs off, timing off, summary off, analyze) select * from t, t1, pt where t1.t2 = t.t2 and t1.tid = ptid;
select * from t, t1, pt where t1.t2 = t.t2 and t1.tid = ptid;
-- Both joined tables can be used for partition elimination. Only partitions
-- that contain matching rows for both joins need to be scanned.
-- have to do some tricks to coerce the planner to choose the plan we want.
begin;
insert into t select i, -100, 'dummy' from generate_series(1,10) i;
insert into t1 select i, -100, 'dummy' from generate_series(1,10) i;
analyze t;
analyze t1;
explain (costs off, timing off, summary off, analyze) select * from t, t1, pt where t1.tid = ptid and t.tid = ptid;
select * from t, t1, pt where t1.tid = ptid and t.tid = ptid;
rollback;
-- One non-joined table contributing to partition elimination in two different
-- partitioned tables
begin;
-- have to force the planner for it to consider the kind of plan we want
-- to test
set local from_collapse_limit = 1;
set local join_collapse_limit = 1;
explain (costs off, timing off, summary off, analyze) select * from t1 inner join (select pt1.*, pt2.ptid as ptid2 from pt as pt1, pt as pt2 WHERE pt1.ptid <= pt2.ptid and pt1.dist = pt2.dist ) as ptx ON t1.dist = ptx.dist and t1.tid = ptx.ptid and t1.tid = ptx.ptid2;
rollback;
--
-- Partitioned table on both sides of the join. This will create a result node as Append node is
-- not projection capable.
--
explain (costs off, timing off, summary off, analyze) select * from pt, pt1 where pt.ptid = pt1.ptid and pt.pt1 = 'hello0' order by pt1.dist;
select * from pt, pt1 where pt.ptid = pt1.ptid and pt.pt1 = 'hello0' order by pt1.dist;
explain (costs off, timing off, summary off, analyze) select count(*) from pt, pt1 where pt.ptid = pt1.ptid and pt.pt1 = 'hello0';
select count(*) from pt, pt1 where pt.ptid = pt1.ptid and pt.pt1 = 'hello0';
--
-- Partition Selector under Material in NestLoopJoin inner side
--
drop table if exists pt;
drop table if exists t;
create table t(id int, a int);
create table pt(id int, b int) DISTRIBUTED BY (id) PARTITION BY RANGE(b) (START (0) END (5) EVERY (1));
insert into t select i, i from generate_series(0,4) i;
insert into pt select i, i from generate_series(0,4) i;
analyze t;
analyze pt;
begin;
set enable_hashjoin=off;
set enable_seqscan=on;
set enable_nestloop=on;
explain (costs off, timing off, summary off, analyze) select * from t, pt where a = b;
select * from t, pt where a = b;
rollback;
--
-- partition selector with 0 tuples and 0 matched partitions
--
drop table if exists t;
drop table if exists pt;
create table t(a int);
create table pt(b int) DISTRIBUTED BY (b) PARTITION BY RANGE(b)
(START (0) END (5) EVERY (1));
begin;
set enable_hashjoin=off; -- foring nestloop join
set enable_nestloop=on;
set enable_seqscan=on;
-- 7 in seg1, 8 in seg2, no data in seg0
insert into t select i from generate_series(7,8) i;
-- 0~2 in seg0, 3~4 in seg 1, no data in seg2
insert into pt select i from generate_series(0,4) i;
-- Insert some more rows to coerce the planner to put 'pt' on the outer
-- side of the join.
insert into t select i from generate_series(7,8) i;
insert into pt select 0 from generate_series(1,1000) g;
analyze t;
analyze pt;
explain (costs off, timing off, summary off, analyze) select * from t, pt where a = b;
select * from t, pt where a = b;
rollback;
--
-- Multi-level partitions
--
drop schema if exists dpe_multi cascade;
create schema dpe_multi;
set search_path='dpe_multi';
set gp_segments_for_planner=2;
set optimizer_segments=2;
create table dim1(dist int, pid int, code text, t1 text);
insert into dim1 values (1, 0, 'OH', 'world1');
insert into dim1 values (1, 1, 'OH', 'world2');
insert into dim1 values (1, 100, 'GA', 'world2'); -- should not have a match at all
analyze dim1;
create table fact1(dist int, pid int, code text, u int)
partition by range(pid)
subpartition by list(code)
subpartition template
(
subpartition ca values('CA'),
subpartition oh values('OH'),
subpartition wa values('WA')
)
(
start (0)
end (4)
every (1)
);
insert into fact1 select 1, i % 4 , 'OH', i from generate_series (1,100) i;
insert into fact1 select 1, i % 4 , 'CA', i + 10000 from generate_series (1,100) i;
--
-- Join on all partitioning columns
--
set gp_dynamic_partition_pruning=off;
explain (costs off, timing off, summary off, analyze) select * from dim1 inner join fact1 on (dim1.pid=fact1.pid and dim1.code=fact1.code) order by fact1.u;
select * from dim1 inner join fact1 on (dim1.pid=fact1.pid and dim1.code=fact1.code) order by fact1.u;
set gp_dynamic_partition_pruning=on;
explain (costs off, timing off, summary off, analyze) select * from dim1 inner join fact1 on (dim1.pid=fact1.pid and dim1.code=fact1.code) order by fact1.u;
select * from dim1 inner join fact1 on (dim1.pid=fact1.pid and dim1.code=fact1.code) order by fact1.u;
--
-- Join on one of the partitioning columns
--
set gp_dynamic_partition_pruning=off;
explain (costs off, timing off, summary off, analyze) select * from dim1 inner join fact1 on (dim1.pid=fact1.pid) order by fact1.u;
select * from dim1 inner join fact1 on (dim1.pid=fact1.pid) order by fact1.u;
set gp_dynamic_partition_pruning=on;
explain (costs off, timing off, summary off, analyze) select * from dim1 inner join fact1 on (dim1.pid=fact1.pid) order by fact1.u;
select * from dim1 inner join fact1 on (dim1.pid=fact1.pid) order by fact1.u;
--
-- Join on the subpartitioning column only
--
set gp_dynamic_partition_pruning=off;
explain (costs off, timing off, summary off, analyze)
select * from dim1 inner join fact1 on (dim1.dist = fact1.dist and dim1.code=fact1.code);
select * from dim1 inner join fact1 on (dim1.dist = fact1.dist and dim1.code=fact1.code);
set gp_dynamic_partition_pruning=on;
explain (costs off, timing off, summary off, analyze)
select * from dim1 inner join fact1 on (dim1.dist = fact1.dist and dim1.code=fact1.code);
select * from dim1 inner join fact1 on (dim1.dist = fact1.dist and dim1.code=fact1.code);
--
-- Join on one of the partitioning columns and static elimination on other
--
set gp_dynamic_partition_pruning=off;
explain (costs off, timing off, summary off, analyze) select * from dim1 inner join fact1 on (dim1.pid=fact1.pid) and fact1.code = 'OH' order by fact1.u;
select * from dim1 inner join fact1 on (dim1.pid=fact1.pid) and fact1.code = 'OH' order by fact1.u;
set gp_dynamic_partition_pruning=on;
explain (costs off, timing off, summary off, analyze) select * from dim1 inner join fact1 on (dim1.pid=fact1.pid) and fact1.code = 'OH' order by fact1.u;
select * from dim1 inner join fact1 on (dim1.pid=fact1.pid) and fact1.code = 'OH' order by fact1.u;
--
-- add aggregates
--
set gp_dynamic_partition_pruning=off;
explain (costs off, timing off, summary off, analyze) select fact1.code, count(*) from dim1 inner join fact1 on (dim1.pid=fact1.pid) group by 1 order by 1;
select fact1.code, count(*) from dim1 inner join fact1 on (dim1.pid=fact1.pid) group by 1 order by 1;
set gp_dynamic_partition_pruning=on;
explain (costs off, timing off, summary off, analyze) select fact1.code, count(*) from dim1 inner join fact1 on (dim1.pid=fact1.pid) group by 1 order by 1;
select fact1.code, count(*) from dim1 inner join fact1 on (dim1.pid=fact1.pid) group by 1 order by 1;
--
-- multi-attribute list partitioning
--
-- Before GPDB 7, we used to support multi-column list partitions natively,
-- and these queries did partition elimination. We don't support that anymore,
-- but emulate that by using a row expression as the partitioning key. You
-- don't get partition elimination with that, however, so these tests are not
-- very interesting anymore.
--
drop schema if exists dpe_malp cascade;
create schema dpe_malp;
set search_path='dpe_malp';
set gp_segments_for_planner=2;
set optimizer_segments=2;
create type malp_key as (i int, j int);
create table malp (i int, j int, t text)
distributed by (i)
partition by list ((row(i, j)::malp_key));
create table malp_p1 partition of malp for values in (row(1, 10));
create table malp_p2 partition of malp for values in (row(2, 20));
create table malp_p3 partition of malp for values in (row(3, 30));
insert into malp select 1, 10, 'hello1';
insert into malp select 1, 10, 'hello2';
insert into malp select 1, 10, 'hello3';
insert into malp select 2, 20, 'hello4';
insert into malp select 2, 20, 'hello5';
insert into malp select 3, 30, 'hello6';
create table dim(i int, j int)
distributed randomly;
insert into dim values(1, 10);
analyze malp;
analyze dim;
-- ORCA doesn't do multi-attribute partitioning currently,so this falls
-- back to the Postgres planner
explain (costs off, timing off, summary off, analyze) select * from dim inner join malp on (dim.i = malp.i);
set gp_dynamic_partition_pruning = off;
select * from dim inner join malp on (dim.i = malp.i);
set gp_dynamic_partition_pruning = on;
select * from dim inner join malp on (dim.i = malp.i);
set gp_dynamic_partition_pruning = on;
-- if only the planner was smart enough, one partition would be chosen
select * from dim inner join malp on (dim.i = malp.i and dim.j = malp.j);
--
-- Plan where the Append that the PartitionSelector affects is not the immediate child
-- of the join.
--
create table apart (id int4, t text) partition by range (id) (start (1) end (1000) every (200));
create table b (id int4, t text);
create table c (id int4, t text);
insert into apart select g, g from generate_series(1, 999) g;
insert into b select g, g from generate_series(1, 5) g;
insert into c select g, g from generate_series(1, 20) g;
analyze apart;
analyze b;
analyze c;
set gp_dynamic_partition_pruning = off;
explain (costs off, timing off, summary off, analyze) select * from apart as a, b, c where a.t = b.t and a.id = c.id;
select * from apart as a, b, c where a.t = b.t and a.id = c.id;
set gp_dynamic_partition_pruning = on;
explain (costs off, timing off, summary off, analyze) select * from apart as a, b, c where a.t = b.t and a.id = c.id;
select * from apart as a, b, c where a.t = b.t and a.id = c.id;
--
-- DPE: assertion failed with window function
--
drop schema if exists dpe_bugs cascade;
create schema dpe_bugs;
set search_path='dpe_bugs';
set gp_segments_for_planner=2;
set optimizer_segments=2;
create table pat(a int, b date) partition by range (b) (start ('2010-01-01') end ('2010-01-05') every (1), default partition other);
insert into pat select i,date '2010-01-01' + i from generate_series(1, 10)i;
create table jpat(a int, b date);
insert into jpat values(1, '2010-01-02');
analyze jpat;
-- start_ignore
-- Known_opt_diff: MPP-21323
-- end_ignore
explain (costs off, timing off, summary off, analyze) select * from (select count(*) over (order by a rows between 1 preceding and 1 following), a, b from jpat)jpat inner join pat using(b);
select * from (select count(*) over (order by a rows between 1 preceding and 1 following), a, b from jpat)jpat inner join pat using(b);
--
-- Partitioning on an expression
--
drop table if exists t;
drop table if exists pt;
create table t(id int, b int);
create table pt(id int, b int) partition by range (id);
create table pt1 partition of pt for values from (1) to (2);
create table pt2 partition of pt for values from (2) to (3);
create table pt3 partition of pt for values from (3) to (4);
create table ptx (id int, b int) partition by list (((b) % 2));
create table ptx_even partition of ptx for values in (0);
create table ptx_odd partition of ptx for values in (1);
alter table pt attach partition ptx for values from (4) to (20);
insert into t values (1, 1);
insert into t values (2, 2);
insert into pt select i, i from generate_series(1,7) i;
analyze t;
analyze pt;
-- Prune on the simple partition columns, but not on the expression
explain (analyze, costs off, timing off, summary off)
select * from pt, t where t.id = pt.id;
insert into t values (4, 4), (6, 6), (8, 8), (10, 10);
explain (analyze, costs off, timing off, summary off)
select * from pt, t where t.id = pt.id;
-- Plan-time pruning based on the 'id' partitioning column, and
-- run-time join pruning based on the expression
explain (analyze, costs off, timing off, summary off)
select * from pt, t where pt.id = 4 and t.id = 4 and (t.b % 2) = (pt.b % 2);
-- Mixed case
insert into pt values (4, 5);
explain (analyze, costs off, timing off, summary off)
select * from pt, t where t.id = pt.id and (t.b % 2) = (pt.b % 2);
--
-- Join pruning on an inequality qual
--
drop table if exists t;
drop table if exists pt;
create table t(dist int, tid int) distributed by (dist);
create table pt(dist int, ptid int) distributed by (dist) partition by range (ptid);
create table pt1 partition of pt for values from (1) to (2);
create table pt2 partition of pt for values from (2) to (3);
create table pt3 partition of pt for values from (3) to (4);
create table pt4 partition of pt for values from (4) to (5);
create table pt5 partition of pt for values from (5) to (6);
create table ptdefault partition of pt default;
insert into t values (0, 4);
insert into t values (0, 3);
insert into pt select 0, i from generate_series(1,9) i;
analyze t;
analyze pt;
explain (analyze, costs off, timing off, summary off)
select * from pt, t where t.dist = pt.dist and t.tid < pt.ptid;
--
-- Test join pruning with a MergeAppend
--
drop table if exists t;
drop table if exists pt;
create table t(dist int, tid int, sk int) distributed by (dist);
create table pt(dist int, ptid int, sk int) distributed by (dist) partition by range (ptid);
create table pt1 partition of pt for values from (1) to (2);
create table pt2 partition of pt for values from (2) to (3);
create table pt3 partition of pt for values from (3) to (4);
create table pt4 partition of pt for values from (4) to (5);
create table pt5 partition of pt for values from (5) to (6);
create table ptdefault partition of pt default;
insert into t values (1, 1, 1);
insert into t values (2, 2, 2);
insert into t select i, i, i from generate_series(5,100) i;
insert into pt select i, i, i from generate_series(1,7) i;
insert into pt select i, i, i from generate_series(1000, 1100) i;
analyze t;
analyze pt;
create index on pt (ptid, sk);
set enable_mergejoin=on;
set enable_seqscan=off;
-- force_explain
explain (analyze, timing off, summary off)
select * from pt, t where t.dist = pt.dist and t.tid = pt.ptid order by t.tid, t.sk;