Skip to content

Commit 3cc0c25

Browse files
committed
Fix runtime partition pruning for HASH partitioned tables
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.
1 parent 7f1b5a6 commit 3cc0c25

File tree

3 files changed

+62
-15
lines changed

3 files changed

+62
-15
lines changed

src/backend/executor/execPartition.c

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,7 +1888,7 @@ ExecInitPruningContext(PartitionPruneContext *context,
18881888
foreach(lc, pruning_steps)
18891889
{
18901890
PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc);
1891-
ListCell *lc2;
1891+
ListCell *lc2 = list_head(step->exprs);
18921892
int keyno;
18931893

18941894
/* not needed for other step kinds */
@@ -1897,22 +1897,27 @@ ExecInitPruningContext(PartitionPruneContext *context,
18971897

18981898
Assert(list_length(step->exprs) <= partnatts);
18991899

1900-
keyno = 0;
1901-
foreach(lc2, step->exprs)
1900+
for (keyno = 0; keyno < partnatts; keyno++)
19021901
{
1903-
Expr *expr = (Expr *) lfirst(lc2);
1902+
if (bms_is_member(keyno, step->nullkeys))
1903+
continue;
19041904

1905-
/* not needed for Consts */
1906-
if (!IsA(expr, Const))
1905+
if (lc2 != NULL)
19071906
{
1908-
int stateidx = PruneCxtStateIdx(partnatts,
1909-
step->step.step_id,
1910-
keyno);
1907+
Expr *expr = lfirst(lc2);
19111908

1912-
context->exprstates[stateidx] =
1913-
ExecInitExpr(expr, context->planstate);
1909+
/* not needed for Consts */
1910+
if (!IsA(expr, Const))
1911+
{
1912+
int stateidx = PruneCxtStateIdx(partnatts,
1913+
step->step.step_id,
1914+
keyno);
1915+
1916+
context->exprstates[stateidx] =
1917+
ExecInitExpr(expr, context->planstate);
1918+
}
1919+
lc2 = lnext(lc2);
19141920
}
1915-
keyno++;
19161921
}
19171922
}
19181923
}

src/test/regress/expected/partition_prune.out

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1907,7 +1907,6 @@ explain (costs off) select * from hp where a = 1 and b = 'abcde' and
19071907
One-Time Filter: false
19081908
(2 rows)
19091909

1910-
drop table hp;
19111910
--
19121911
-- Test runtime partition pruning
19131912
--
@@ -2133,6 +2132,29 @@ explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2);
21332132
Filter: ((b >= $1) AND (b <= $2) AND (a < $0))
21342133
(10 rows)
21352134

2135+
--
2136+
-- Test runtime pruning with hash partitioned tables
2137+
--
2138+
-- recreate partitions dropped above
2139+
create table hp1 partition of hp for values with (modulus 4, remainder 1);
2140+
create table hp2 partition of hp for values with (modulus 4, remainder 2);
2141+
create table hp3 partition of hp for values with (modulus 4, remainder 3);
2142+
-- Ensure we correctly prune unneeded partitions when there is an IS NULL qual
2143+
prepare hp_q1 (text) as
2144+
select * from hp where a is null and b = $1;
2145+
set plan_cache_mode = force_generic_plan;
2146+
explain (costs off) execute hp_q1('xxx');
2147+
QUERY PLAN
2148+
--------------------------------------------
2149+
Append
2150+
Subplans Removed: 3
2151+
-> Seq Scan on hp2
2152+
Filter: ((a IS NULL) AND (b = $1))
2153+
(4 rows)
2154+
2155+
reset plan_cache_mode;
2156+
deallocate hp_q1;
2157+
drop table hp;
21362158
-- Test a backwards Append scan
21372159
create table list_part (a int) partition by list (a);
21382160
create table list_part1 partition of list_part for values in (1);

src/test/regress/sql/partition_prune.sql

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,6 @@ drop table hp2;
371371
explain (costs off) select * from hp where a = 1 and b = 'abcde' and
372372
(c = 2 or c = 3);
373373

374-
drop table hp;
375-
376374
--
377375
-- Test runtime partition pruning
378376
--
@@ -451,6 +449,28 @@ execute ab_q3 (1, 8);
451449

452450
explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2);
453451

452+
--
453+
-- Test runtime pruning with hash partitioned tables
454+
--
455+
456+
-- recreate partitions dropped above
457+
create table hp1 partition of hp for values with (modulus 4, remainder 1);
458+
create table hp2 partition of hp for values with (modulus 4, remainder 2);
459+
create table hp3 partition of hp for values with (modulus 4, remainder 3);
460+
461+
-- Ensure we correctly prune unneeded partitions when there is an IS NULL qual
462+
prepare hp_q1 (text) as
463+
select * from hp where a is null and b = $1;
464+
465+
set plan_cache_mode = force_generic_plan;
466+
467+
explain (costs off) execute hp_q1('xxx');
468+
469+
reset plan_cache_mode;
470+
deallocate hp_q1;
471+
472+
drop table hp;
473+
454474
-- Test a backwards Append scan
455475
create table list_part (a int) partition by list (a);
456476
create table list_part1 partition of list_part for values in (1);

0 commit comments

Comments
 (0)