]> granicus.if.org Git - postgresql/commitdiff
Fix partition pruning with IS [NOT] NULL clauses
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Mon, 16 Jul 2018 22:38:09 +0000 (18:38 -0400)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Mon, 16 Jul 2018 22:38:09 +0000 (18:38 -0400)
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 <ashutosh.bapat@enterprisedb.com>
Co-authored-by: Dilip Kumar <dilipbalaut@gmail.com>
Co-authored-by: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Co-authored-by: Álvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Reviewed-by: amul sul <sulamul@gmail.com>
Discussion: https://postgr.es/m/CAFjFpRc7qjLUfXLVBBC_HAnx644sjTYM=qVoT3TJ840HPbsTXw@mail.gmail.com

src/backend/partitioning/partprune.c
src/test/regress/expected/partition_prune.out
src/test/regress/sql/partition_prune.sql

index cdc61a89974b0acf7620d576065ba6b2c80d8886..354eb0d4e6055af35d1ddb9e0c8cc829a57a4391 100644 (file)
@@ -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
index d15f1d37f131bc3b87e783eeb84d957a704a8910..022b7c55c7da9af56e69b591839de7e017acf0c8 100644 (file)
@@ -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;
index b8e823d5620efb616f2546d6d30d1d5c563cfde8..2357f02cde9395730d7f6ca3d568c7a942490664 100644 (file)
@@ -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;