From fb2b61a21e141843697de7982c9f6485effcd915 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Mon, 16 Jul 2018 18:38:09 -0400 Subject: [PATCH] Fix partition pruning with IS [NOT] NULL clauses MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The original code was unable to prune partitions that could not possibly contain NULL values, when the query specified less than all columns in a multicolumn partition key. Reorder the if-tests so that it is, and add more commentary and regression tests. Reported-by: Ashutosh Bapat Co-authored-by: Dilip Kumar Co-authored-by: Amit Langote Co-authored-by: Álvaro Herrera Reviewed-by: Ashutosh Bapat Reviewed-by: amul sul Discussion: https://postgr.es/m/CAFjFpRc7qjLUfXLVBBC_HAnx644sjTYM=qVoT3TJ840HPbsTXw@mail.gmail.com --- src/backend/partitioning/partprune.c | 78 ++++++++++--------- src/test/regress/expected/partition_prune.out | 41 ++++++++++ src/test/regress/sql/partition_prune.sql | 7 ++ 3 files changed, 90 insertions(+), 36 deletions(-) diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index cdc61a8997..354eb0d4e6 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -853,54 +853,60 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context, } } - /* - * If generate_opsteps is set to false it means no OpExprs were directly - * present in the input list. + /*----------- + * Now generate some (more) pruning steps. We have three strategies: + * + * 1) Generate pruning steps based on IS NULL clauses: + * a) For list partitioning, null partition keys can only be found in + * the designated null-accepting partition, so if there are IS NULL + * clauses containing partition keys we should generate a pruning + * step that gets rid of all partitions but that one. We can + * disregard any OpExpr we may have found. + * b) For range partitioning, only the default partition can contain + * NULL values, so the same rationale applies. + * c) For hash partitioning, we only apply this strategy if we have + * IS NULL clauses for all the keys. Strategy 2 below will take + * care of the case where some keys have OpExprs and others have + * IS NULL clauses. + * + * 2) If not, generate steps based on OpExprs we have (if any). + * + * 3) If this doesn't work either, we may be able to generate steps to + * prune just the null-accepting partition (if one exists), if we have + * IS NOT NULL clauses for all partition keys. */ - if (!generate_opsteps) + if (!bms_is_empty(nullkeys) && + (part_scheme->strategy == PARTITION_STRATEGY_LIST || + part_scheme->strategy == PARTITION_STRATEGY_RANGE || + (part_scheme->strategy == PARTITION_STRATEGY_HASH && + bms_num_members(nullkeys) == part_scheme->partnatts))) { - /* - * 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. - */ - 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); - } - - /* - * 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; + PartitionPruneStep *step; - step = gen_prune_step_op(context, InvalidStrategy, - false, NIL, NIL, NULL); - result = lappend(result, step); - } + /* Strategy 1 */ + step = gen_prune_step_op(context, InvalidStrategy, + false, NIL, NIL, nullkeys); + result = lappend(result, step); } - else + else if (generate_opsteps) { PartitionPruneStep *step; - /* Generate pruning steps from OpExpr clauses in keyclauses. */ + /* Strategy 2 */ step = gen_prune_steps_from_opexps(part_scheme, context, keyclauses, nullkeys); if (step != NULL) result = lappend(result, step); } + else if (bms_num_members(notnullkeys) == part_scheme->partnatts) + { + PartitionPruneStep *step; + + /* Strategy 3 */ + step = gen_prune_step_op(context, InvalidStrategy, + false, NIL, NIL, NULL); + result = lappend(result, step); + } /* * Finally, results from all entries appearing in result should be diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index d15f1d37f1..022b7c55c7 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -993,6 +993,47 @@ explain (costs off) select * from mc2p where a = 1 and b > 1; Filter: ((b > 1) AND (a = 1)) (3 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 +------------------------------------------- + Append + -> Seq Scan on mc2p_default + Filter: ((b IS NULL) AND (a = 1)) +(3 rows) + +explain (costs off) select * from mc2p where a is null and b is null; + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on mc2p_default + Filter: ((a IS NULL) AND (b IS NULL)) +(3 rows) + +explain (costs off) select * from mc2p where a is null and b = 1; + QUERY PLAN +------------------------------------------- + Append + -> Seq Scan on mc2p_default + Filter: ((a IS NULL) AND (b = 1)) +(3 rows) + +explain (costs off) select * from mc2p where a is null; + QUERY PLAN +-------------------------------- + Append + -> Seq Scan on mc2p_default + Filter: (a IS NULL) +(3 rows) + +explain (costs off) select * from mc2p where b is null; + QUERY PLAN +-------------------------------- + Append + -> Seq Scan on mc2p_default + Filter: (b IS NULL) +(3 rows) + -- boolean partitioning create table boolpart (a bool) partition by list (a); create table boolpart_default partition of boolpart default; diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index b8e823d562..2357f02cde 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -137,6 +137,13 @@ explain (costs off) select * from mc2p where a = 2 and b < 1; explain (costs off) select * from mc2p where a > 1; explain (costs off) select * from mc2p where a = 1 and b > 1; +-- all partitions but the default one should be pruned +explain (costs off) select * from mc2p where a = 1 and b is null; +explain (costs off) select * from mc2p where a is null and b is null; +explain (costs off) select * from mc2p where a is null and b = 1; +explain (costs off) select * from mc2p where a is null; +explain (costs off) select * from mc2p where b is null; + -- boolean partitioning create table boolpart (a bool) partition by list (a); create table boolpart_default partition of boolpart default; -- 2.40.0