diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 59f34f5cac..855873eddf 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -83,6 +83,17 @@ typedef enum PartClauseMatchStatus PARTCLAUSE_UNSUPPORTED } PartClauseMatchStatus; +/* + * PruneEnvironment + * Provides the pruning code context about where the pruning steps + * will be executed. + */ +typedef enum PruneEnvironment +{ + PRUNEENV_PLANNER, + PRUNEENV_EXECUTOR +} PruneEnvironment; + /* * GeneratePruningStepsContext * Information about the current state of generation of "pruning steps" @@ -116,11 +127,11 @@ static List *make_partitionedrel_pruneinfo(PlannerInfo *root, int *relid_subplan_map, List *partitioned_rels, List *prunequal, Bitmapset **matchedsubplans); -static List *gen_partprune_steps(RelOptInfo *rel, List *clauses, - bool *contradictory); +static List *gen_partprune_steps(RelOptInfo *rel, PruneEnvironment pruneenv, + List *clauses, bool *contradictory); static List *gen_partprune_steps_internal(GeneratePruningStepsContext *context, - RelOptInfo *rel, List *clauses, - bool *contradictory); + RelOptInfo *rel, PruneEnvironment pruneenv, + List *clauses, bool *contradictory); static PartitionPruneStep *gen_prune_step_op(GeneratePruningStepsContext *context, StrategyNumber opstrategy, bool op_is_ne, List *exprs, List *cmpfns, Bitmapset *nullkeys); @@ -132,6 +143,7 @@ static PartitionPruneStep *gen_prune_steps_from_opexps(PartitionScheme part_sche List **keyclauses, Bitmapset *nullkeys); static PartClauseMatchStatus match_clause_to_partition_key(RelOptInfo *rel, GeneratePruningStepsContext *context, + PruneEnvironment pruneenv, Expr *clause, Expr *partkey, int partkeyidx, bool *clause_is_not_null, PartClauseInfo **pc, List **clause_steps); @@ -410,8 +422,8 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, } /* Convert pruning qual to pruning steps. */ - pruning_steps = gen_partprune_steps(subpart, partprunequal, - &contradictory); + pruning_steps = gen_partprune_steps(subpart, PRUNEENV_EXECUTOR, + partprunequal, &contradictory); if (contradictory) { @@ -523,13 +535,16 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, /* * gen_partprune_steps * Process 'clauses' (a rel's baserestrictinfo list of clauses) and return - * a list of "partition pruning steps" + * a list of "partition pruning steps". + * + * 'pruneenv' defines where the pruning steps will be executed. * * If the clauses in the input list are contradictory or there is a * pseudo-constant "false", *contradictory is set to true upon return. */ static List * -gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) +gen_partprune_steps(RelOptInfo *rel, PruneEnvironment pruneenv, List *clauses, + bool *contradictory) { GeneratePruningStepsContext context; @@ -567,7 +582,8 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) } /* Down into the rabbit-hole. */ - gen_partprune_steps_internal(&context, rel, clauses, contradictory); + gen_partprune_steps_internal(&context, rel, pruneenv, clauses, + contradictory); return context.steps; } @@ -605,7 +621,8 @@ prune_append_rel_partitions(RelOptInfo *rel) * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ - pruning_steps = gen_partprune_steps(rel, clauses, &contradictory); + pruning_steps = gen_partprune_steps(rel, PRUNEENV_PLANNER, clauses, + &contradictory); if (contradictory) return NULL; @@ -761,8 +778,8 @@ get_matching_partitions(PartitionPruneContext *context, List *pruning_steps) */ static List * gen_partprune_steps_internal(GeneratePruningStepsContext *context, - RelOptInfo *rel, List *clauses, - bool *contradictory) + RelOptInfo *rel, PruneEnvironment pruneenv, + List *clauses, bool *contradictory) { PartitionScheme part_scheme = rel->part_scheme; List *keyclauses[PARTITION_MAX_KEYS]; @@ -822,6 +839,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context, argsteps = gen_partprune_steps_internal(context, rel, + pruneenv, list_make1(arg), &arg_contradictory); if (!arg_contradictory) @@ -906,7 +924,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context, * recurse and later combine the component partitions sets * using a combine step. */ - argsteps = gen_partprune_steps_internal(context, rel, args, + argsteps = gen_partprune_steps_internal(context, rel, pruneenv, args, contradictory); if (*contradictory) return NIL; @@ -948,7 +966,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context, PartClauseInfo *pc = NULL; List *clause_steps = NIL; - switch (match_clause_to_partition_key(rel, context, + switch (match_clause_to_partition_key(rel, context, pruneenv, clause, partkey, i, &clause_is_not_null, &pc, &clause_steps)) @@ -1562,6 +1580,7 @@ gen_prune_steps_from_opexps(PartitionScheme part_scheme, static PartClauseMatchStatus match_clause_to_partition_key(RelOptInfo *rel, GeneratePruningStepsContext *context, + PruneEnvironment pruneenv, Expr *clause, Expr *partkey, int partkeyidx, bool *clause_is_not_null, PartClauseInfo **pc, List **clause_steps) @@ -1652,12 +1671,31 @@ match_clause_to_partition_key(RelOptInfo *rel, */ /* - * We can't prune using an expression with Vars. (Report failure as - * UNSUPPORTED, not NOMATCH: as in the no-commutator case above, we - * now know there are Vars on both sides, so it's no good.) + * When pruning in the planner, we only support pruning using + * constants. Immutable functions will have been folded to consts + * already so we could be dealing with a Var or an unsupported + * function call. */ - if (contain_var_clause((Node *) expr)) - return PARTCLAUSE_UNSUPPORTED; + if (pruneenv == PRUNEENV_PLANNER) + { + if (!IsA(expr, Const)) + return PARTCLAUSE_UNSUPPORTED; + } + else + { + /* + * Otherwise, non-consts are supported, but we can't prune using + * an expression with Vars. (Report failure as UNSUPPORTED, not + * NOMATCH: as in the no-commutator case above, we now know there + * are Vars on both sides, so it's no good.) + */ + if (contain_var_clause((Node *) expr)) + return PARTCLAUSE_UNSUPPORTED; + + /* And we must reject anything containing a volatile function. */ + if (contain_volatile_functions((Node *) expr)) + return PARTCLAUSE_UNSUPPORTED; + } /* * Only allow strict operators. This will guarantee nulls are @@ -1666,10 +1704,6 @@ match_clause_to_partition_key(RelOptInfo *rel, if (!op_strict(opno)) return PARTCLAUSE_UNSUPPORTED; - /* We can't use any volatile expressions to prune partitions. */ - if (contain_volatile_functions((Node *) expr)) - return PARTCLAUSE_UNSUPPORTED; - /* * See if the operator is relevant to the partitioning opfamily. * @@ -1945,7 +1979,7 @@ match_clause_to_partition_key(RelOptInfo *rel, /* Finally, generate steps */ *clause_steps = - gen_partprune_steps_internal(context, rel, elem_clauses, + gen_partprune_steps_internal(context, rel, pruneenv, elem_clauses, &contradictory); if (contradictory) return PARTCLAUSE_MATCH_CONTRADICT; diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index bd64bed8fc..aafb9d3932 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -141,6 +141,28 @@ explain (costs off) select * from lp where a not in ('a', 'd'); Filter: (a <> ALL ('{a,d}'::bpchar[])) (9 rows) +create function return_e_stable() returns char as $$begin return 'e'::char; end;$$ stable language plpgsql; +-- Ensure we don't prune during planning using the return value of a stable function. +explain (costs off) select * from lp where a = return_e_stable(); + QUERY PLAN +----------------------------------------- + Append + Subplans Removed: 5 + -> Seq Scan on lp_ef + Filter: (a = return_e_stable()) +(4 rows) + +create function return_e_immutable() returns char as $$begin return 'e'::char; end;$$ immutable language plpgsql; +-- Ensure we do prune during planning for an immutable function. +explain (costs off) select * from lp where a = return_e_immutable(); + QUERY PLAN +----------------------------- + Seq Scan on lp_ef + Filter: (a = 'e'::bpchar) +(2 rows) + +drop function return_e_stable(); +drop function return_e_immutable(); -- collation matches the partitioning collation, pruning works create table coll_pruning (a text collate "C") partition by list (a); create table coll_pruning_a partition of coll_pruning for values in ('a'); diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index 246c6274af..d84dbdc6d9 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -21,6 +21,19 @@ explain (costs off) select * from lp where a <> 'g'; explain (costs off) select * from lp where a <> 'a' and a <> 'd'; explain (costs off) select * from lp where a not in ('a', 'd'); +create function return_e_stable() returns char as $$begin return 'e'::char; end;$$ stable language plpgsql; + +-- Ensure we don't prune during planning using the return value of a stable function. +explain (costs off) select * from lp where a = return_e_stable(); + +create function return_e_immutable() returns char as $$begin return 'e'::char; end;$$ immutable language plpgsql; + +-- Ensure we do prune during planning for an immutable function. +explain (costs off) select * from lp where a = return_e_immutable(); + +drop function return_e_stable(); +drop function return_e_immutable(); + -- collation matches the partitioning collation, pruning works create table coll_pruning (a text collate "C") partition by list (a); create table coll_pruning_a partition of coll_pruning for values in ('a');