]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix runtime partition pruning for HASH partitioned tables
authorDavid Rowley <drowley@postgresql.org>
Thu, 12 Oct 2023 12:14:46 +0000 (01:14 +1300)
committerDavid Rowley <drowley@postgresql.org>
Thu, 12 Oct 2023 12:14:46 +0000 (01:14 +1300)
This could only affect HASH partitioned tables with at least 2 partition
key columns.

If partition pruning was delayed until execution and the query contained
an IS NULL qual on one of the partitioned keys, and some subsequent
partitioned key was being compared to a non-Const, then this could result
in a crash due to the incorrect keyno being used to calculate the
stateidx for the expression evaluation code.

Here we fix this by properly skipping partitioned keys which have a
nullkey set.  Effectively, this must be the same as what's going on
inside perform_pruning_base_step().

Sergei Glukhov also provided a patch, but that's not what's being used
here.

Reported-by: Sergei Glukhov
Reviewed-by: tender wang, Sergei Glukhov
Discussion: https://postgr.es/m/d05b26fa-af54-27e1-f693-6c31590802fa@postgrespro.ru
Backpatch-through: 11, where runtime partition pruning was added.

src/backend/executor/execPartition.c
src/test/regress/expected/partition_prune.out
src/test/regress/sql/partition_prune.sql

index 4698f74f91065c64e67667c36db9fa336d1f1ffb..873e65f3f6d96c513e82f4224bda338eec870892 100644 (file)
@@ -1888,7 +1888,7 @@ ExecInitPruningContext(PartitionPruneContext *context,
        foreach(lc, pruning_steps)
        {
                PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc);
-               ListCell   *lc2;
+               ListCell   *lc2 = list_head(step->exprs);
                int                     keyno;
 
                /* not needed for other step kinds */
@@ -1897,22 +1897,27 @@ ExecInitPruningContext(PartitionPruneContext *context,
 
                Assert(list_length(step->exprs) <= partnatts);
 
-               keyno = 0;
-               foreach(lc2, step->exprs)
+               for (keyno = 0; keyno < partnatts; keyno++)
                {
-                       Expr       *expr = (Expr *) lfirst(lc2);
+                       if (bms_is_member(keyno, step->nullkeys))
+                               continue;
 
-                       /* not needed for Consts */
-                       if (!IsA(expr, Const))
+                       if (lc2 != NULL)
                        {
-                               int                     stateidx = PruneCxtStateIdx(partnatts,
-                                                                                                               step->step.step_id,
-                                                                                                               keyno);
+                               Expr *expr = lfirst(lc2);
 
-                               context->exprstates[stateidx] =
-                                       ExecInitExpr(expr, context->planstate);
+                               /* not needed for Consts */
+                               if (!IsA(expr, Const))
+                               {
+                                       int                     stateidx = PruneCxtStateIdx(partnatts,
+                                                                                                                       step->step.step_id,
+                                                                                                                       keyno);
+
+                                       context->exprstates[stateidx] =
+                                               ExecInitExpr(expr, context->planstate);
+                               }
+                               lc2 = lnext(lc2);
                        }
-                       keyno++;
                }
        }
 }
index feb52f9a9c69302f3284450e16088db5a58c3d38..083dd1259ef03574bb9a03178080a6554dea7df9 100644 (file)
@@ -1907,7 +1907,6 @@ explain (costs off) select * from hp where a = 1 and b = 'abcde' and
    One-Time Filter: false
 (2 rows)
 
-drop table hp;
 --
 -- Test runtime partition pruning
 --
@@ -2133,6 +2132,29 @@ explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2);
          Filter: ((b >= $1) AND (b <= $2) AND (a < $0))
 (10 rows)
 
+--
+-- Test runtime pruning with hash partitioned tables
+--
+-- recreate partitions dropped above
+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);
+create table hp3 partition of hp for values with (modulus 4, remainder 3);
+-- Ensure we correctly prune unneeded partitions when there is an IS NULL qual
+prepare hp_q1 (text) as
+select * from hp where a is null and b = $1;
+set plan_cache_mode = force_generic_plan;
+explain (costs off) execute hp_q1('xxx');
+                 QUERY PLAN                 
+--------------------------------------------
+ Append
+   Subplans Removed: 3
+   ->  Seq Scan on hp2
+         Filter: ((a IS NULL) AND (b = $1))
+(4 rows)
+
+reset plan_cache_mode;
+deallocate hp_q1;
+drop table hp;
 -- Test a backwards Append scan
 create table list_part (a int) partition by list (a);
 create table list_part1 partition of list_part for values in (1);
index 01572424e9365ce4ea31620d94b779bf7a086851..765f3c68a438d177df2f29c79b107740d9b6925e 100644 (file)
@@ -371,8 +371,6 @@ drop table hp2;
 explain (costs off) select * from hp where a = 1 and b = 'abcde' and
   (c = 2 or c = 3);
 
-drop table hp;
-
 --
 -- Test runtime partition pruning
 --
@@ -451,6 +449,28 @@ execute ab_q3 (1, 8);
 
 explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2);
 
+--
+-- Test runtime pruning with hash partitioned tables
+--
+
+-- recreate partitions dropped above
+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);
+create table hp3 partition of hp for values with (modulus 4, remainder 3);
+
+-- Ensure we correctly prune unneeded partitions when there is an IS NULL qual
+prepare hp_q1 (text) as
+select * from hp where a is null and b = $1;
+
+set plan_cache_mode = force_generic_plan;
+
+explain (costs off) execute hp_q1('xxx');
+
+reset plan_cache_mode;
+deallocate hp_q1;
+
+drop table hp;
+
 -- Test a backwards Append scan
 create table list_part (a int) partition by list (a);
 create table list_part1 partition of list_part for values in (1);