1
0
mirror of https://github.com/postgres/postgres.git synced 2025-04-22 23:02:54 +03:00

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.
This commit is contained in:
David Rowley 2023-10-13 01:12:31 +13:00
parent dab5538f0b
commit d9e46dfb78
3 changed files with 69 additions and 27 deletions

View File

@ -2108,7 +2108,7 @@ InitPartitionPruneContext(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 */
@ -2117,34 +2117,39 @@ InitPartitionPruneContext(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);
/*
* When planstate is NULL, pruning_steps is known not to
* contain any expressions that depend on the parent plan.
* Information of any available EXTERN parameters must be
* passed explicitly in that case, which the caller must have
* made available via econtext.
*/
if (planstate == NULL)
context->exprstates[stateidx] =
ExecInitExprWithParams(expr,
econtext->ecxt_param_list_info);
else
context->exprstates[stateidx] =
ExecInitExpr(expr, context->planstate);
/* not needed for Consts */
if (!IsA(expr, Const))
{
int stateidx = PruneCxtStateIdx(partnatts,
step->step.step_id,
keyno);
/*
* When planstate is NULL, pruning_steps is known not to
* contain any expressions that depend on the parent plan.
* Information of any available EXTERN parameters must be
* passed explicitly in that case, which the caller must
* have made available via econtext.
*/
if (planstate == NULL)
context->exprstates[stateidx] =
ExecInitExprWithParams(expr,
econtext->ecxt_param_list_info);
else
context->exprstates[stateidx] =
ExecInitExpr(expr, context->planstate);
}
lc2 = lnext(step->exprs, lc2);
}
keyno++;
}
}
}

View File

@ -1948,7 +1948,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
--
@ -2070,6 +2069,27 @@ 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;
explain (costs off) execute hp_q1('xxx');
QUERY PLAN
--------------------------------------------
Append
Subplans Removed: 3
-> Seq Scan on hp2 hp_1
Filter: ((a IS NULL) AND (b = $1))
(4 rows)
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);

View File

@ -384,8 +384,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
--
@ -436,6 +434,25 @@ select a from ab where b between $1 and $2 and a < (select 3);
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;
explain (costs off) execute hp_q1('xxx');
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);