From bca944478a372b9dc54781de5b142ffd79aeeee6 Mon Sep 17 00:00:00 2001 From: amit Date: Wed, 15 May 2019 16:45:03 +0900 Subject: [PATCH 2/2] Fix bugs in pruning with composite range partition keys * Force using equality (= operator) semantics when comparing clause expressions against partition bounds if the expression for the last matched key cannot be evaluated in a given pruning context; for example, if the expression is stable and pruning is invoked by the planner * Ignore keys after a given key for which we detected a clause containing a non-inclusive operator --- src/backend/partitioning/partprune.c | 93 +++++++++++------------ src/test/regress/expected/partition_prune.out | 103 ++++++++++++++++++++++++++ src/test/regress/sql/partition_prune.sql | 40 ++++++++++ 3 files changed, 190 insertions(+), 46 deletions(-) diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index be3292c4b6..9577088bf4 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -1170,17 +1170,24 @@ gen_prune_steps_from_opexps(PartitionScheme part_scheme, List *opsteps = NIL; List *btree_clauses[BTMaxStrategyNumber + 1], *hash_clauses[HTMaxStrategyNumber + 1]; - bool need_next_less, - need_next_eq, - need_next_greater; + bool consider_next_key; int i; + consider_next_key = true; memset(btree_clauses, 0, sizeof(btree_clauses)); memset(hash_clauses, 0, sizeof(hash_clauses)); for (i = 0; i < part_scheme->partnatts; i++) { List *clauselist = keyclauses[i]; - bool consider_next_key = true; + + /* + * If the previous key was found to be compared using such an operator + * that comparisons of subsequent key columns against partition bounds + * that occurs during partition pruning would be redundant, then don't + * bother generating pruning steps for them. + */ + if (!consider_next_key) + break; /* * To be useful for pruning, we must have clauses for a prefix of @@ -1200,7 +1207,6 @@ gen_prune_steps_from_opexps(PartitionScheme part_scheme, clauselist == NIL && !bms_is_member(i, nullkeys)) return NULL; - need_next_eq = need_next_less = need_next_greater = true; foreach(lc, clauselist) { PartClauseInfo *pc = (PartClauseInfo *) lfirst(lc); @@ -1216,13 +1222,13 @@ gen_prune_steps_from_opexps(PartitionScheme part_scheme, &lefttype, &righttype); + switch (part_scheme->strategy) { case PARTITION_STRATEGY_LIST: case PARTITION_STRATEGY_RANGE: { PartClauseInfo *last = NULL; - bool inclusive = false; /* * Add this clause to the list of clauses to be used @@ -1240,35 +1246,14 @@ gen_prune_steps_from_opexps(PartitionScheme part_scheme, lappend(btree_clauses[pc->op_strategy], pc); /* - * We may not need the next clause if they're of - * certain strategy. + * We won't need the clause for the next key if the + * clause for the current key contains a non-inclusive + * operator. */ - switch (pc->op_strategy) - { - case BTLessEqualStrategyNumber: - inclusive = true; - /* fall through */ - case BTLessStrategyNumber: - if (!inclusive) - need_next_eq = need_next_less = false; - break; - case BTEqualStrategyNumber: - /* always accept clauses for the next key. */ - break; - case BTGreaterEqualStrategyNumber: - inclusive = true; - /* fall through */ - case BTGreaterStrategyNumber: - if (!inclusive) - need_next_eq = need_next_greater = false; - break; - } + if (pc->op_strategy == BTLessStrategyNumber || + pc->op_strategy == BTGreaterStrategyNumber) + consider_next_key = false; - /* We may want to change our mind. */ - if (consider_next_key) - consider_next_key = (need_next_eq || - need_next_less || - need_next_greater); break; } @@ -1285,13 +1270,6 @@ gen_prune_steps_from_opexps(PartitionScheme part_scheme, break; } } - - /* - * If we've decided that clauses for subsequent partition keys - * wouldn't be useful for pruning, don't search any further. - */ - if (!consider_next_key) - break; } /* @@ -2780,7 +2758,7 @@ get_matching_range_bounds(PartitionPruneContext *context, /* * Look for the greatest bound that is < or <= lookup value and - * set minoff to its offset. + * set maxoff to its offset. */ off = partition_range_datum_bsearch(partsupfunc, partcollation, @@ -3162,11 +3140,34 @@ perform_pruning_base_step(PartitionPruneContext *context, opstep->nullkeys); case PARTITION_STRATEGY_RANGE: - return get_matching_range_bounds(context, - opstep->opstrategy, - values, nvalues, - partsupfunc, - opstep->nullkeys); + { + int opstrategy = opstep->opstrategy; + + /* + * If we got values for only a subset (prefix) of the + * expressions in opstep->exprs, adjust the strategy to + * be used for pruning. Originally, it would be the strategy + * of the operator comparing the *last* expression against the + * corresponding partition key, but it would be wrong to use + * the same strategy now that we'll be comparing the values of + * only a prefix of expressions against the partition bounds. + * Use equality strategy, because all expressions in the + * prefix must have been extracted from clauses containing + * equality (=) or inclusive operators (<=, >=) anyway. + * + * Note: this can only occur during executor startup runtime + * pruning, that too only if opstep->exprs contains a mix of + * PARAM_EXTERN and PARAM_EXEC params. It's too late to add + * an Assert for that in v11. + */ + if (nvalues < list_length(opstep->exprs)) + opstrategy = BTEqualStrategyNumber; + return get_matching_range_bounds(context, + opstrategy, + values, nvalues, + partsupfunc, + opstep->nullkeys); + } default: elog(ERROR, "unexpected partition strategy: %d", diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 7e4208ca67..902bfc7787 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2989,6 +2989,109 @@ select * from stable_qual_pruning where a < '2019-02-01'::timestamptz; (4 rows) drop table stable_qual_pruning; +-- +-- Check that pruning with composite range partitioning works correctly when +-- a combination of runtime parameters is specified, not all of whose values +-- are available at the same time +-- +create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); +create table mc3p1 partition of mc3p for values from (1, 1, 1) to (2, minvalue, minvalue); +create table mc3p2 partition of mc3p for values from (2, minvalue, minvalue) to (3, maxvalue, maxvalue); +insert into mc3p values (1, 1, 1), (2, 1, 1); +analyze; +-- baseline result with pruning disabled +set enable_partition_pruning to off; +select * from mc3p where a = 1 and abs(b) < 3; + a | b | c +---+---+--- + 1 | 1 | 1 +(1 row) + +-- with pruning enabled +reset enable_partition_pruning; +prepare init_exec_prune_q1 as select * from mc3p where a = $1 and abs(b) < (select 3); +execute init_exec_prune_q1 (1); + a | b | c +---+---+--- + 1 | 1 | 1 +(1 row) + +execute init_exec_prune_q1 (1); + a | b | c +---+---+--- + 1 | 1 | 1 +(1 row) + +execute init_exec_prune_q1 (1); + a | b | c +---+---+--- + 1 | 1 | 1 +(1 row) + +execute init_exec_prune_q1 (1); + a | b | c +---+---+--- + 1 | 1 | 1 +(1 row) + +execute init_exec_prune_q1 (1); + a | b | c +---+---+--- + 1 | 1 | 1 +(1 row) + +explain (analyze, costs off, summary off, timing off) execute init_exec_prune_q1 (1); + QUERY PLAN +------------------------------------------------- + Append (actual rows=1 loops=1) + InitPlan 1 (returns $0) + -> Result (actual rows=1 loops=1) + Subplans Removed: 1 + -> Seq Scan on mc3p1 (actual rows=1 loops=1) + Filter: ((a = $1) AND (abs(b) < $0)) +(6 rows) + +execute init_exec_prune_q1 (1); + a | b | c +---+---+--- + 1 | 1 | 1 +(1 row) + +-- +-- Check that pruning with composite range partitioning works correctly when +-- it must ignore clauses for trailing keys once it has seen a clause with +-- non-inclusive operator for an earlier key +-- +-- baseline result with pruning disabled +set enable_partition_pruning to off; +select * from mc3p where a < 3 and abs(b) = 1; + a | b | c +---+---+--- + 1 | 1 | 1 + 2 | 1 | 1 +(2 rows) + +-- with pruning enabled +reset enable_partition_pruning; +explain (analyze, costs off, summary off, timing off) select * from mc3p where a < 3 and abs(b) = 1; + QUERY PLAN +------------------------------------------------- + Append (actual rows=2 loops=1) + -> Seq Scan on mc3p1 (actual rows=1 loops=1) + Filter: ((a < 3) AND (abs(b) = 1)) + -> Seq Scan on mc3p2 (actual rows=1 loops=1) + Filter: ((a < 3) AND (abs(b) = 1)) +(5 rows) + +select * from mc3p where a < 3 and abs(b) = 1; + a | b | c +---+---+--- + 1 | 1 | 1 + 2 | 1 | 1 +(2 rows) + +deallocate init_exec_prune_q1; +drop table mc3p; -- Ensure runtime pruning works with initplans params with boolean types create table boolvalues (value bool not null); insert into boolvalues values('t'),('f'); diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index c26a9e12d5..49f6db12f3 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -731,6 +731,46 @@ select * from stable_qual_pruning where a < '2019-02-01'::timestamptz; drop table stable_qual_pruning; +-- +-- Check that pruning with composite range partitioning works correctly when +-- a combination of runtime parameters is specified, not all of whose values +-- are available at the same time +-- +create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); +create table mc3p1 partition of mc3p for values from (1, 1, 1) to (2, minvalue, minvalue); +create table mc3p2 partition of mc3p for values from (2, minvalue, minvalue) to (3, maxvalue, maxvalue); +insert into mc3p values (1, 1, 1), (2, 1, 1); +analyze; +-- baseline result with pruning disabled +set enable_partition_pruning to off; +select * from mc3p where a = 1 and abs(b) < 3; +-- with pruning enabled +reset enable_partition_pruning; +prepare init_exec_prune_q1 as select * from mc3p where a = $1 and abs(b) < (select 3); +execute init_exec_prune_q1 (1); +execute init_exec_prune_q1 (1); +execute init_exec_prune_q1 (1); +execute init_exec_prune_q1 (1); +execute init_exec_prune_q1 (1); +explain (analyze, costs off, summary off, timing off) execute init_exec_prune_q1 (1); +execute init_exec_prune_q1 (1); + +-- +-- Check that pruning with composite range partitioning works correctly when +-- it must ignore clauses for trailing keys once it has seen a clause with +-- non-inclusive operator for an earlier key +-- +-- baseline result with pruning disabled +set enable_partition_pruning to off; +select * from mc3p where a < 3 and abs(b) = 1; +-- with pruning enabled +reset enable_partition_pruning; +explain (analyze, costs off, summary off, timing off) select * from mc3p where a < 3 and abs(b) = 1; +select * from mc3p where a < 3 and abs(b) = 1; + +deallocate init_exec_prune_q1; +drop table mc3p; + -- Ensure runtime pruning works with initplans params with boolean types create table boolvalues (value bool not null); insert into boolvalues values('t'),('f'); -- 2.11.0