On 2018-Jul-13, Ashutosh Bapat wrote:

> On Fri, Jul 13, 2018 at 1:15 PM, Amit Langote
> <langote_amit...@lab.ntt.co.jp> wrote:
> >>
> >> I don't think this is true. When equality conditions and IS NULL clauses 
> >> cover
> >> all partition keys of a hash partitioned table and do not have 
> >> contradictory
> >> clauses, we should be able to find the partition which will remain 
> >> unpruned.
> >
> > I was trying to say the same thing, but maybe the comment doesn't like it.
> >  How about this:
> >
> > +     * For hash partitioning, if we found IS NULL clauses for some keys and
> > +     * OpExpr's for others, gen_prune_steps_from_opexps() will generate the
> > +     * necessary pruning steps if all partition keys are taken care of.
> > +     * If we found only IS NULL clauses, then we can generate the pruning
> > +     * step here but only if all partition keys are covered.
> >
> 
> It's still confusing a bit. I think "taken care of" doesn't convey the
> same meaning as "covered". I don't think the second sentence is
> necessary, it talks about one special case of the first sentence. But
> this is better than the prior version.

Hmm, let me reword this comment completely.  How about the attached?

I also changed the order of the tests, putting the 'generate_opsteps'
one in the middle instead of at the end (so the not-null one doesn't
test that boolean again).  AFAICS it's logically the same, and it makes
more sense to me that way.

I also changed the tests so that they apply to the existing mc2p table,
which is identical to the one Amit was adding.

> > How about if we explicitly spell out the strategies like this:
> >
> > +    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)))
> 
> I still do not understand why do we need (part_scheme->strategy ==
> PARTITION_STRATEGY_HASH && bms_num_members(nullkeys) ==
> part_scheme->partnatts) there. I thought that this will be handled by
> code, which deals with null keys and op_exprs together, somewhere
> else.

I'm not sure I understand this question.  This strategy applies to hash
partitioning only if we have null tests for all keys, so there are no
op_exprs.  If there are any, there's no point in checking them.

Now this case is a fun one:
select * from mc2p where a in (1, 2) and b is null;
Note that no partitions are pruned in that case; yet if you do this:
select * from mc2p where a in (1) and b is null;
then it does prune.  I think the reason for this is that the
PARTCLAUSE_MATCH_STEPS is ... pretty special.

> > Actually, there is only one case where the pruning step generated by that
> > block of code is useful -- to prune a list partition that accepts only
> > nulls.  List partitioning only allows one partition, key so this works,
> > but let's say only accidentally.  I modified the condition as follows:
> >
> > +    else if (!generate_opsteps && !bms_is_empty(notnullkeys) &&
> > +             bms_num_members(notnullkeys) == part_scheme->partnatts)
> >
> 
> Hmm. Ok. May be it's better to explicitly say that it's only useful in
> case of list partitioned table.

Yeah, maybe.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/src/backend/partitioning/partprune.c 
b/src/backend/partitioning/partprune.c
index cdc61a8997..e44df8ac94 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -853,54 +853,62 @@ 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 may be able to (1) generate pruning steps based on IS NULL 
clauses,
+        * if there are enough of them:
+        * a) For list partitioning, null partition keys can only be found in 
the
+        *    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.
+        * b) For range partitioning, the same applies.  Doing this check first
+        *    means we'll disregard OpExprs that may have been found for other
+        *    keys, but that's fine, because it is not possible to prune range
+        *    partitions with a combination of null and non-null keys.
+        * c) For hash partitioning, we only apply this strategy if we have
+        *    IS NULL clauses for all the keys.  Strategy 2 will take care of 
the
+        *    case where some keys have OpExprs and others have IS NULL clauses.
+        *
+        * If not, then (2) generate steps based on OpExprs we have (if any).
+        *
+        * If this doesn't work either, we may be able to (3) 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)
-       {
-               /*
-                * 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;
-
-                       step = gen_prune_step_op(context, InvalidStrategy,
-                                                                        false, 
NIL, NIL, NULL);
-                       result = lappend(result, step);
-               }
-       }
-       else
+       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)))
        {
                PartitionPruneStep *step;
 
-               /* Generate pruning steps from OpExpr clauses in keyclauses. */
+               /* Strategy 1 */
+               step = gen_prune_step_op(context, InvalidStrategy,
+                                                                false, NIL, 
NIL, nullkeys);
+               result = lappend(result, step);
+       }
+       else if (generate_opsteps)
+       {
+               PartitionPruneStep *step;
+
+               /* Strategy 2 */
                step = gen_prune_steps_from_opexps(part_scheme, context,
                                                                                
   keyclauses, nullkeys);
                if (step != NULL)
                        result = lappend(result, step);
        }
+       else if (!bms_is_empty(notnullkeys) &&
+                        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;

Reply via email to