On 2018/07/12 14:32, Amit Langote wrote: > Thanks Ashutosh for reporting and Dilip for the analysis and the patch. > > On 2018/07/11 21:39, Dilip Kumar wrote: >> On Wed, Jul 11, 2018 at 5:36 PM, amul sul <sula...@gmail.com> wrote: >>> On Wed, Jul 11, 2018 at 5:10 PM Dilip Kumar <dilipbal...@gmail.com> wrote: >> >>>> >>> I am not sure that I have understand the following comments >>> 11 + * Generate one prune step for the information derived from IS NULL, >>> 12 + * if any. To prune hash partitions, we must have found IS NULL >>> 13 + * clauses for all partition keys. >>> 14 */ >>> >>> I am not sure that I have understood this -- no such restriction >>> required to prune the hash partitions, if I am not missing anything. >> >> Maybe it's not very clear but this is the original comments I have >> retained. Just moved it out of the (!generate_opsteps) condition. >> >> Just the explain this comment consider below example, >> >> create table hp (a int, b text) partition by hash (a int, b text); >> 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); >> >> postgres=# insert into hp values (1, null); >> INSERT 0 1 >> postgres=# insert into hp values (2, null); >> INSERT 0 1 >> postgres=# select tableoid::regclass, * from hp; >> tableoid | a | b >> ----------+---+--- >> hp1 | 1 | >> hp2 | 2 | >> (2 rows) >> >> Now, if we query based on "b is null" then we can not decide which >> partition should be pruned whereas in case >> of other schemes, it will go to default partition so we can prune all >> other partitions. > > That's right. By generating a pruning step with only nullkeys set, we are > effectively discarding OpExprs that may have been found for some partition > keys. That's fine for list/range partitioning, because nulls can only be > found in a designated partition, so it's okay to prune all other > partitions and for that it's enough to generate the pruning step like > that. For hash partitioning, nulls could be contained in any partition so > it's not okay to discard OpExpr's like that. We can generate pruning > steps with combination of null and non-null keys in the hash partitioning > case if there are any OpExprs. > > I think your fix is correct. I slightly modified it along with updating > nearby comments and added regression tests.
I updated regression tests to reduce lines. There is no point in repeating tests like v2 patch did. Thanks, Amit
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index cdc61a8997..d9612bf65b 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -854,44 +854,42 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context, } /* - * If generate_opsteps is set to false it means no OpExprs were directly - * present in the input list. + * For list and range partitioning, null partition keys can only be found + * in one designated partition, so if there are IS NULL clauses containing + * partition keys we should generate a pruning step that gets rid of all + * partitions but the special null-accepting partitions. For range + * partitioning, that means we will end up disregarding OpExpr's that may + * have been found for some other keys, but that's fine, because it is not + * possible to prune range partitions with a combination of null and + * non-null keys. + * + * For hash partitioning however, it is possible to combine null and non- + * null keys in a pruning step, so do this only if *all* partition keys + * are involved in IS NULL clauses. */ - if (!generate_opsteps) + if (!bms_is_empty(nullkeys) && + (part_scheme->strategy != PARTITION_STRATEGY_HASH || + bms_num_members(nullkeys) == part_scheme->partnatts)) + { + PartitionPruneStep *step; + + step = gen_prune_step_op(context, InvalidStrategy, + false, NIL, NIL, nullkeys); + result = lappend(result, step); + } + else if (!generate_opsteps && !bms_is_empty(notnullkeys)) { /* - * Generate one prune step for the information derived from IS NULL, - * if any. To prune hash partitions, we must have found IS NULL - * clauses for all partition keys. + * There are no OpExpr's, but there are IS NOT NULL clauses, which + * can be used to eliminate the null-partition-key-only partition. */ - if (!bms_is_empty(nullkeys) && - (part_scheme->strategy != PARTITION_STRATEGY_HASH || - bms_num_members(nullkeys) == part_scheme->partnatts)) - { - PartitionPruneStep *step; + PartitionPruneStep *step; - step = gen_prune_step_op(context, InvalidStrategy, - false, NIL, NIL, nullkeys); - result = lappend(result, step); - } - - /* - * Note that for IS NOT NULL clauses, simply having step suffices; - * there is no need to propagate the exact details of which keys are - * required to be NOT NULL. Hash partitioning expects to see actual - * values to perform any pruning. - */ - if (!bms_is_empty(notnullkeys) && - part_scheme->strategy != PARTITION_STRATEGY_HASH) - { - PartitionPruneStep *step; - - step = gen_prune_step_op(context, InvalidStrategy, - false, NIL, NIL, NULL); - result = lappend(result, step); - } + step = gen_prune_step_op(context, InvalidStrategy, + false, NIL, NIL, NULL); + result = lappend(result, step); } - else + else if (generate_opsteps) { PartitionPruneStep *step; diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index d15f1d37f1..a3b21130e3 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -3125,3 +3125,51 @@ explain (costs off) select * from pp_temp_parent where a = 2; (3 rows) drop table pp_temp_parent; +-- multi-column range partitioning and pruning with IS NULL clauses on some +-- columns +create table pp_multirange (a int, b int) partition by range (a, b); +create table pp_multirange1 partition of pp_multirange for values from (1, 1) to (100, 100); +create table pp_multirange2 partition of pp_multirange for values from (100, 100) to (200, 200); +create table pp_multirange_def partition of pp_multirange default; +-- all partitions but the default one should be pruned +explain (costs off) select * from pp_multirange where a = 1 and b is null; + QUERY PLAN +------------------------------------------- + Append + -> Seq Scan on pp_multirange_def + Filter: ((b IS NULL) AND (a = 1)) +(3 rows) + +explain (costs off) select * from pp_multirange where a is null and b is null; + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on pp_multirange_def + Filter: ((a IS NULL) AND (b IS NULL)) +(3 rows) + +explain (costs off) select * from pp_multirange where a is null and b = 1; + QUERY PLAN +------------------------------------------- + Append + -> Seq Scan on pp_multirange_def + Filter: ((a IS NULL) AND (b = 1)) +(3 rows) + +explain (costs off) select * from pp_multirange where a is null; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on pp_multirange_def + Filter: (a IS NULL) +(3 rows) + +explain (costs off) select * from pp_multirange where b is null; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on pp_multirange_def + Filter: (b IS NULL) +(3 rows) + +drop table pp_multirange; diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index b8e823d562..cf56fdac40 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -823,3 +823,19 @@ create temp table pp_temp_part_def partition of pp_temp_parent default; explain (costs off) select * from pp_temp_parent where true; explain (costs off) select * from pp_temp_parent where a = 2; drop table pp_temp_parent; + +-- multi-column range partitioning and pruning with IS NULL clauses on some +-- columns +create table pp_multirange (a int, b int) partition by range (a, b); +create table pp_multirange1 partition of pp_multirange for values from (1, 1) to (100, 100); +create table pp_multirange2 partition of pp_multirange for values from (100, 100) to (200, 200); +create table pp_multirange_def partition of pp_multirange default; + +-- all partitions but the default one should be pruned +explain (costs off) select * from pp_multirange where a = 1 and b is null; +explain (costs off) select * from pp_multirange where a is null and b is null; +explain (costs off) select * from pp_multirange where a is null and b = 1; +explain (costs off) select * from pp_multirange where a is null; +explain (costs off) select * from pp_multirange where b is null; + +drop table pp_multirange;