On Wed, 11 Oct 2023 at 22:09, Sergei Glukhov <s.gluk...@postgrespro.ru> wrote:
> Thanks for fixing this!
>
> I verified that issues are fixed.

Thanks for having a look.

Unfortunately, I'd not long sent the last email and realised that the
step_lastkeyno parameter is now unused and can just be removed from
both get_steps_using_prefix() and get_steps_using_prefix_recurse().
This requires some comment rewriting so I've attempted to do that too
in the attached updated version.

David
diff --git a/src/backend/partitioning/partprune.c 
b/src/backend/partitioning/partprune.c
index 7179b22a05..20b5e01c9e 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -167,7 +167,6 @@ static List 
*get_steps_using_prefix(GeneratePruningStepsContext *context,
                                                                        bool 
step_op_is_ne,
                                                                        Expr 
*step_lastexpr,
                                                                        Oid 
step_lastcmpfn,
-                                                                       int 
step_lastkeyno,
                                                                        
Bitmapset *step_nullkeys,
                                                                        List 
*prefix);
 static List *get_steps_using_prefix_recurse(GeneratePruningStepsContext 
*context,
@@ -175,7 +174,6 @@ static List 
*get_steps_using_prefix_recurse(GeneratePruningStepsContext *context
                                                                                
        bool step_op_is_ne,
                                                                                
        Expr *step_lastexpr,
                                                                                
        Oid step_lastcmpfn,
-                                                                               
        int step_lastkeyno,
                                                                                
        Bitmapset *step_nullkeys,
                                                                                
        List *prefix,
                                                                                
        ListCell *start,
@@ -1531,7 +1529,6 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext 
*context,
                                                                                
                                          pc->op_is_ne,
                                                                                
                                          pc->expr,
                                                                                
                                          pc->cmpfn,
-                                                                               
                                          0,
                                                                                
                                          NULL,
                                                                                
                                          NIL);
                                                        opsteps = 
list_concat(opsteps, pc_steps);
@@ -1657,7 +1654,6 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext 
*context,
                                                                                
                                          pc->op_is_ne,
                                                                                
                                          pc->expr,
                                                                                
                                          pc->cmpfn,
-                                                                               
                                          pc->keyno,
                                                                                
                                          NULL,
                                                                                
                                          prefix);
                                                        opsteps = 
list_concat(opsteps, pc_steps);
@@ -1731,7 +1727,6 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext 
*context,
                                                                                
                   false,
                                                                                
                   pc->expr,
                                                                                
                   pc->cmpfn,
-                                                                               
                   pc->keyno,
                                                                                
                   nullkeys,
                                                                                
                   prefix);
                                                opsteps = list_concat(opsteps, 
pc_steps);
@@ -2350,25 +2345,27 @@ 
match_clause_to_partition_key(GeneratePruningStepsContext *context,
 
 /*
  * get_steps_using_prefix
- *             Generate list of PartitionPruneStepOp steps each consisting of 
given
+ *             Generate a list of PartitionPruneStepOp steps each consisting 
of given
  *             opstrategy
  *
  * To generate steps, step_lastexpr and step_lastcmpfn are appended to
  * expressions and cmpfns, respectively, extracted from the clauses in
- * 'prefix'.  Actually, since 'prefix' may contain multiple clauses for the
- * same partition key column, we must generate steps for various combinations
- * of the clauses of different keys.
+ * 'prefix'.  Since 'prefix' may contain multiple clauses for each partition
+ * key, and since each step can only contain a single clause for each
+ * partition key, when there are multiple clauses for any given key, we must
+ * generate steps for all combinations of the clauses.
  *
  * For list/range partitioning, callers must ensure that step_nullkeys is
  * NULL, and that prefix contains at least one clause for each of the
- * partition keys earlier than one specified in step_lastkeyno if it's
- * greater than zero.  For hash partitioning, step_nullkeys is allowed to be
- * non-NULL, but they must ensure that prefix contains at least one clause
- * for each of the partition keys other than those specified in step_nullkeys
- * and step_lastkeyno.
- *
- * For both cases, callers must also ensure that clauses in prefix are sorted
- * in ascending order of their partition key numbers.
+ * partition keys prior to the key that 'step_lastexpr' belongs to.
+ *
+ * For hash partitioning, callers must ensure that 'prefix' contains at least
+ * one clause for each of the partition keys apart from the final key.  A bit
+ * set in step_nullkeys can substitute clauses in the 'prefix' list for any
+ * given key.  Both may not be specified.
+ *
+ * For each of the above cases, callers must ensure that PartClauseInfos in
+ * 'prefix' are sorted in ascending order of keyno.
  */
 static List *
 get_steps_using_prefix(GeneratePruningStepsContext *context,
@@ -2376,7 +2373,6 @@ get_steps_using_prefix(GeneratePruningStepsContext 
*context,
                                           bool step_op_is_ne,
                                           Expr *step_lastexpr,
                                           Oid step_lastcmpfn,
-                                          int step_lastkeyno,
                                           Bitmapset *step_nullkeys,
                                           List *prefix)
 {
@@ -2397,13 +2393,12 @@ get_steps_using_prefix(GeneratePruningStepsContext 
*context,
                return list_make1(step);
        }
 
-       /* Recurse to generate steps for various combinations. */
+       /* Recurse to generate steps for every combination of clauses. */
        return get_steps_using_prefix_recurse(context,
                                                                                
  step_opstrategy,
                                                                                
  step_op_is_ne,
                                                                                
  step_lastexpr,
                                                                                
  step_lastcmpfn,
-                                                                               
  step_lastkeyno,
                                                                                
  step_nullkeys,
                                                                                
  prefix,
                                                                                
  list_head(prefix),
@@ -2413,9 +2408,8 @@ get_steps_using_prefix(GeneratePruningStepsContext 
*context,
 /*
  * get_steps_using_prefix_recurse
  *             Recursively generate combinations of clauses for different 
partition
- *             keys and start generating steps upon reaching clauses for the 
greatest
- *             column that is less than the one for which we're currently 
generating
- *             steps (that is, step_lastkeyno)
+ *             keys and generate PartitionPruneSteps for each combination of
+ *             PartClauseInfos in the 'prefix' list.
  *
  * 'prefix' is the list of PartClauseInfos.
  * 'start' is where we should start iterating for the current invocation.
@@ -2428,7 +2422,6 @@ 
get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
                                                           bool step_op_is_ne,
                                                           Expr *step_lastexpr,
                                                           Oid step_lastcmpfn,
-                                                          int step_lastkeyno,
                                                           Bitmapset 
*step_nullkeys,
                                                           List *prefix,
                                                           ListCell *start,
@@ -2438,14 +2431,17 @@ 
get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
        List       *result = NIL;
        ListCell   *lc;
        int                     cur_keyno;
+       int                     final_keyno;
 
        /* Actually, recursion would be limited by PARTITION_MAX_KEYS. */
        check_stack_depth();
 
-       /* Check if we need to recurse. */
        Assert(start != NULL);
        cur_keyno = ((PartClauseInfo *) lfirst(start))->keyno;
-       if (cur_keyno < step_lastkeyno - 1)
+       final_keyno = ((PartClauseInfo *) llast(prefix))->keyno;
+
+       /* Check if we need to recurse. */
+       if (cur_keyno < final_keyno)
        {
                PartClauseInfo *pc;
                ListCell   *next_start;
@@ -2493,7 +2489,6 @@ 
get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
                                                                                
                           step_op_is_ne,
                                                                                
                           step_lastexpr,
                                                                                
                           step_lastcmpfn,
-                                                                               
                           step_lastkeyno,
                                                                                
                           step_nullkeys,
                                                                                
                           prefix,
                                                                                
                           next_start,
diff --git a/src/test/regress/expected/partition_prune.out 
b/src/test/regress/expected/partition_prune.out
index 36791293ee..1bfdf37657 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -4024,20 +4024,327 @@ explain (costs off) select * from rp_prefix_test3 
where a >= 1 and b >= 1 and b
    Filter: ((a >= 1) AND (b >= 1) AND (d >= 0) AND (b = 2) AND (c = 2))
 (2 rows)
 
+drop table rp_prefix_test1;
+drop table rp_prefix_test2;
+drop table rp_prefix_test3;
 create table hp_prefix_test (a int, b int, c int, d int) partition by hash (a 
part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d 
part_test_int4_ops);
-create table hp_prefix_test_p1 partition of hp_prefix_test for values with 
(modulus 2, remainder 0);
-create table hp_prefix_test_p2 partition of hp_prefix_test for values with 
(modulus 2, remainder 1);
--- Test that get_steps_using_prefix() handles non-NULL step_nullkeys
-explain (costs off) select * from hp_prefix_test where a = 1 and b is null and 
c = 1 and d = 1;
+-- create 16 partitions
+select 'create table hp_prefix_test_p' || x::text || ' partition of 
hp_prefix_test for values with (modulus 16, remainder ' || x::text || ');'
+from generate_Series(0,15) x;
+                                                ?column?                       
                          
+---------------------------------------------------------------------------------------------------------
+ create table hp_prefix_test_p0 partition of hp_prefix_test for values with 
(modulus 16, remainder 0);
+ create table hp_prefix_test_p1 partition of hp_prefix_test for values with 
(modulus 16, remainder 1);
+ create table hp_prefix_test_p2 partition of hp_prefix_test for values with 
(modulus 16, remainder 2);
+ create table hp_prefix_test_p3 partition of hp_prefix_test for values with 
(modulus 16, remainder 3);
+ create table hp_prefix_test_p4 partition of hp_prefix_test for values with 
(modulus 16, remainder 4);
+ create table hp_prefix_test_p5 partition of hp_prefix_test for values with 
(modulus 16, remainder 5);
+ create table hp_prefix_test_p6 partition of hp_prefix_test for values with 
(modulus 16, remainder 6);
+ create table hp_prefix_test_p7 partition of hp_prefix_test for values with 
(modulus 16, remainder 7);
+ create table hp_prefix_test_p8 partition of hp_prefix_test for values with 
(modulus 16, remainder 8);
+ create table hp_prefix_test_p9 partition of hp_prefix_test for values with 
(modulus 16, remainder 9);
+ create table hp_prefix_test_p10 partition of hp_prefix_test for values with 
(modulus 16, remainder 10);
+ create table hp_prefix_test_p11 partition of hp_prefix_test for values with 
(modulus 16, remainder 11);
+ create table hp_prefix_test_p12 partition of hp_prefix_test for values with 
(modulus 16, remainder 12);
+ create table hp_prefix_test_p13 partition of hp_prefix_test for values with 
(modulus 16, remainder 13);
+ create table hp_prefix_test_p14 partition of hp_prefix_test for values with 
(modulus 16, remainder 14);
+ create table hp_prefix_test_p15 partition of hp_prefix_test for values with 
(modulus 16, remainder 15);
+(16 rows)
+
+\gexec
+create table hp_prefix_test_p0 partition of hp_prefix_test for values with 
(modulus 16, remainder 0);
+create table hp_prefix_test_p1 partition of hp_prefix_test for values with 
(modulus 16, remainder 1);
+create table hp_prefix_test_p2 partition of hp_prefix_test for values with 
(modulus 16, remainder 2);
+create table hp_prefix_test_p3 partition of hp_prefix_test for values with 
(modulus 16, remainder 3);
+create table hp_prefix_test_p4 partition of hp_prefix_test for values with 
(modulus 16, remainder 4);
+create table hp_prefix_test_p5 partition of hp_prefix_test for values with 
(modulus 16, remainder 5);
+create table hp_prefix_test_p6 partition of hp_prefix_test for values with 
(modulus 16, remainder 6);
+create table hp_prefix_test_p7 partition of hp_prefix_test for values with 
(modulus 16, remainder 7);
+create table hp_prefix_test_p8 partition of hp_prefix_test for values with 
(modulus 16, remainder 8);
+create table hp_prefix_test_p9 partition of hp_prefix_test for values with 
(modulus 16, remainder 9);
+create table hp_prefix_test_p10 partition of hp_prefix_test for values with 
(modulus 16, remainder 10);
+create table hp_prefix_test_p11 partition of hp_prefix_test for values with 
(modulus 16, remainder 11);
+create table hp_prefix_test_p12 partition of hp_prefix_test for values with 
(modulus 16, remainder 12);
+create table hp_prefix_test_p13 partition of hp_prefix_test for values with 
(modulus 16, remainder 13);
+create table hp_prefix_test_p14 partition of hp_prefix_test for values with 
(modulus 16, remainder 14);
+create table hp_prefix_test_p15 partition of hp_prefix_test for values with 
(modulus 16, remainder 15);
+-- insert one row for each test to perform.
+insert into hp_prefix_test
+select
+  case a when 0 then null else 1 end,
+  case b when 0 then null else 2 end,
+  case c when 0 then null else 3 end,
+  case d when 0 then null else 4 end
+from
+  generate_series(0,1) a,
+  generate_series(0,1) b,
+  generate_Series(0,1) c,
+  generate_Series(0,1) d;
+-- Ensure partition pruning works correctly for each combination of IS NULL
+-- and equality quals.
+select
+  'explain (costs off) select tableoid::regclass,* from hp_prefix_test where ' 
||
+  string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' 
else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos)
+from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), 
generate_Series(0,15) g(s)
+group by g.s
+order by g.s;
+                                                           ?column?            
                                                
+-------------------------------------------------------------------------------------------------------------------------------
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a 
is null and b is null and c is null and d is null
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b is null and c is null and d is null
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a 
is null and b = 2 and c is null and d is null
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b = 2 and c is null and d is null
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a 
is null and b is null and c = 3 and d is null
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b is null and c = 3 and d is null
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a 
is null and b = 2 and c = 3 and d is null
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b = 2 and c = 3 and d is null
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a 
is null and b is null and c is null and d = 4
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b is null and c is null and d = 4
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a 
is null and b = 2 and c is null and d = 4
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b = 2 and c is null and d = 4
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a 
is null and b is null and c = 3 and d = 4
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b is null and c = 3 and d = 4
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a 
is null and b = 2 and c = 3 and d = 4
+ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b = 2 and c = 3 and d = 4
+(16 rows)
+
+\gexec
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is 
null and b is null and c is null and d is null
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p0 hp_prefix_test
+   Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d IS NULL))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b is null and c is null and d is null
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p1 hp_prefix_test
+   Filter: ((b IS NULL) AND (c IS NULL) AND (d IS NULL) AND (a = 1))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is 
null and b = 2 and c is null and d is null
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p2 hp_prefix_test
+   Filter: ((a IS NULL) AND (c IS NULL) AND (d IS NULL) AND (b = 2))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b = 2 and c is null and d is null
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p12 hp_prefix_test
+   Filter: ((c IS NULL) AND (d IS NULL) AND (a = 1) AND (b = 2))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is 
null and b is null and c = 3 and d is null
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p3 hp_prefix_test
+   Filter: ((a IS NULL) AND (b IS NULL) AND (d IS NULL) AND (c = 3))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b is null and c = 3 and d is null
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p15 hp_prefix_test
+   Filter: ((b IS NULL) AND (d IS NULL) AND (a = 1) AND (c = 3))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is 
null and b = 2 and c = 3 and d is null
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p12 hp_prefix_test
+   Filter: ((a IS NULL) AND (d IS NULL) AND (b = 2) AND (c = 3))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b = 2 and c = 3 and d is null
                          QUERY PLAN                          
 -------------------------------------------------------------
- Seq Scan on hp_prefix_test_p1 hp_prefix_test
-   Filter: ((b IS NULL) AND (a = 1) AND (c = 1) AND (d = 1))
+ Seq Scan on hp_prefix_test_p5 hp_prefix_test
+   Filter: ((d IS NULL) AND (a = 1) AND (b = 2) AND (c = 3))
 (2 rows)
 
-drop table rp_prefix_test1;
-drop table rp_prefix_test2;
-drop table rp_prefix_test3;
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is 
null and b is null and c is null and d = 4
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p4 hp_prefix_test
+   Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d = 4))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b is null and c is null and d = 4
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p14 hp_prefix_test
+   Filter: ((b IS NULL) AND (c IS NULL) AND (a = 1) AND (d = 4))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is 
null and b = 2 and c is null and d = 4
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p13 hp_prefix_test
+   Filter: ((a IS NULL) AND (c IS NULL) AND (b = 2) AND (d = 4))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b = 2 and c is null and d = 4
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p6 hp_prefix_test
+   Filter: ((c IS NULL) AND (a = 1) AND (b = 2) AND (d = 4))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is 
null and b is null and c = 3 and d = 4
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p12 hp_prefix_test
+   Filter: ((a IS NULL) AND (b IS NULL) AND (c = 3) AND (d = 4))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b is null and c = 3 and d = 4
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p5 hp_prefix_test
+   Filter: ((b IS NULL) AND (a = 1) AND (c = 3) AND (d = 4))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is 
null and b = 2 and c = 3 and d = 4
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Seq Scan on hp_prefix_test_p6 hp_prefix_test
+   Filter: ((a IS NULL) AND (b = 2) AND (c = 3) AND (d = 4))
+(2 rows)
+
+explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 
1 and b = 2 and c = 3 and d = 4
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Seq Scan on hp_prefix_test_p4 hp_prefix_test
+   Filter: ((a = 1) AND (b = 2) AND (c = 3) AND (d = 4))
+(2 rows)
+
+-- And ensure we get exactly 1 row from each.
+select
+  'select tableoid::regclass,* from hp_prefix_test where ' ||
+  string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' 
else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos)
+from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), 
generate_Series(0,15) g(s)
+group by g.s
+order by g.s;
+                                                 ?column?                      
                            
+-----------------------------------------------------------------------------------------------------------
+ select tableoid::regclass,* from hp_prefix_test where a is null and b is null 
and c is null and d is null
+ select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and 
c is null and d is null
+ select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and 
c is null and d is null
+ select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c 
is null and d is null
+ select tableoid::regclass,* from hp_prefix_test where a is null and b is null 
and c = 3 and d is null
+ select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and 
c = 3 and d is null
+ select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and 
c = 3 and d is null
+ select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 
3 and d is null
+ select tableoid::regclass,* from hp_prefix_test where a is null and b is null 
and c is null and d = 4
+ select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and 
c is null and d = 4
+ select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and 
c is null and d = 4
+ select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c 
is null and d = 4
+ select tableoid::regclass,* from hp_prefix_test where a is null and b is null 
and c = 3 and d = 4
+ select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and 
c = 3 and d = 4
+ select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and 
c = 3 and d = 4
+ select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 
3 and d = 4
+(16 rows)
+
+\gexec
+select tableoid::regclass,* from hp_prefix_test where a is null and b is null 
and c is null and d is null
+     tableoid      | a | b | c | d 
+-------------------+---+---+---+---
+ hp_prefix_test_p0 |   |   |   |  
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and 
c is null and d is null
+     tableoid      | a | b | c | d 
+-------------------+---+---+---+---
+ hp_prefix_test_p1 | 1 |   |   |  
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and 
c is null and d is null
+     tableoid      | a | b | c | d 
+-------------------+---+---+---+---
+ hp_prefix_test_p2 |   | 2 |   |  
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is 
null and d is null
+      tableoid      | a | b | c | d 
+--------------------+---+---+---+---
+ hp_prefix_test_p12 | 1 | 2 |   |  
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a is null and b is null 
and c = 3 and d is null
+     tableoid      | a | b | c | d 
+-------------------+---+---+---+---
+ hp_prefix_test_p3 |   |   | 3 |  
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and 
c = 3 and d is null
+      tableoid      | a | b | c | d 
+--------------------+---+---+---+---
+ hp_prefix_test_p15 | 1 |   | 3 |  
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and 
c = 3 and d is null
+      tableoid      | a | b | c | d 
+--------------------+---+---+---+---
+ hp_prefix_test_p12 |   | 2 | 3 |  
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 
3 and d is null
+     tableoid      | a | b | c | d 
+-------------------+---+---+---+---
+ hp_prefix_test_p5 | 1 | 2 | 3 |  
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a is null and b is null 
and c is null and d = 4
+     tableoid      | a | b | c | d 
+-------------------+---+---+---+---
+ hp_prefix_test_p4 |   |   |   | 4
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and 
c is null and d = 4
+      tableoid      | a | b | c | d 
+--------------------+---+---+---+---
+ hp_prefix_test_p14 | 1 |   |   | 4
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and 
c is null and d = 4
+      tableoid      | a | b | c | d 
+--------------------+---+---+---+---
+ hp_prefix_test_p13 |   | 2 |   | 4
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is 
null and d = 4
+     tableoid      | a | b | c | d 
+-------------------+---+---+---+---
+ hp_prefix_test_p6 | 1 | 2 |   | 4
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a is null and b is null 
and c = 3 and d = 4
+      tableoid      | a | b | c | d 
+--------------------+---+---+---+---
+ hp_prefix_test_p12 |   |   | 3 | 4
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and 
c = 3 and d = 4
+     tableoid      | a | b | c | d 
+-------------------+---+---+---+---
+ hp_prefix_test_p5 | 1 |   | 3 | 4
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and 
c = 3 and d = 4
+     tableoid      | a | b | c | d 
+-------------------+---+---+---+---
+ hp_prefix_test_p6 |   | 2 | 3 | 4
+(1 row)
+
+select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 
3 and d = 4
+     tableoid      | a | b | c | d 
+-------------------+---+---+---+---
+ hp_prefix_test_p4 | 1 | 2 | 3 | 4
+(1 row)
+
 drop table hp_prefix_test;
 --
 -- Check that gen_partprune_steps() detects self-contradiction from clauses
diff --git a/src/test/regress/sql/partition_prune.sql 
b/src/test/regress/sql/partition_prune.sql
index d23133fe43..6b4039179f 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -1188,16 +1188,49 @@ explain (costs off) select * from rp_prefix_test3 where 
a >= 1 and b >= 1 and b
 -- that the caller arranges clauses in that prefix in the required order)
 explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and 
b = 2 and c = 2 and d >= 0;
 
-create table hp_prefix_test (a int, b int, c int, d int) partition by hash (a 
part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d 
part_test_int4_ops);
-create table hp_prefix_test_p1 partition of hp_prefix_test for values with 
(modulus 2, remainder 0);
-create table hp_prefix_test_p2 partition of hp_prefix_test for values with 
(modulus 2, remainder 1);
-
--- Test that get_steps_using_prefix() handles non-NULL step_nullkeys
-explain (costs off) select * from hp_prefix_test where a = 1 and b is null and 
c = 1 and d = 1;
-
 drop table rp_prefix_test1;
 drop table rp_prefix_test2;
 drop table rp_prefix_test3;
+
+create table hp_prefix_test (a int, b int, c int, d int) partition by hash (a 
part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d 
part_test_int4_ops);
+
+-- create 16 partitions
+select 'create table hp_prefix_test_p' || x::text || ' partition of 
hp_prefix_test for values with (modulus 16, remainder ' || x::text || ');'
+from generate_Series(0,15) x;
+\gexec
+
+-- insert one row for each test to perform.
+insert into hp_prefix_test
+select
+  case a when 0 then null else 1 end,
+  case b when 0 then null else 2 end,
+  case c when 0 then null else 3 end,
+  case d when 0 then null else 4 end
+from
+  generate_series(0,1) a,
+  generate_series(0,1) b,
+  generate_Series(0,1) c,
+  generate_Series(0,1) d;
+
+-- Ensure partition pruning works correctly for each combination of IS NULL
+-- and equality quals.
+select
+  'explain (costs off) select tableoid::regclass,* from hp_prefix_test where ' 
||
+  string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' 
else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos)
+from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), 
generate_Series(0,15) g(s)
+group by g.s
+order by g.s;
+\gexec
+
+-- And ensure we get exactly 1 row from each.
+select
+  'select tableoid::regclass,* from hp_prefix_test where ' ||
+  string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' 
else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos)
+from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), 
generate_Series(0,15) g(s)
+group by g.s
+order by g.s;
+\gexec
+
 drop table hp_prefix_test;
 
 --

Reply via email to