diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 59f34f5cac..5e1329a5e4 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -39,6 +39,7 @@ #include "access/nbtree.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" #include "miscadmin.h" @@ -95,6 +96,8 @@ typedef struct GeneratePruningStepsContext { int next_step_id; List *steps; + bool forplanner; /* true when generating steps to be used + * during query planning */ } GeneratePruningStepsContext; /* The result of performing one PartitionPruneStep */ @@ -117,7 +120,7 @@ static List *make_partitionedrel_pruneinfo(PlannerInfo *root, List *partitioned_rels, List *prunequal, Bitmapset **matchedsubplans); static List *gen_partprune_steps(RelOptInfo *rel, List *clauses, - bool *contradictory); + bool forplanner, bool *contradictory); static List *gen_partprune_steps_internal(GeneratePruningStepsContext *context, RelOptInfo *rel, List *clauses, bool *contradictory); @@ -409,8 +412,13 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, targetpart->relids); } - /* Convert pruning qual to pruning steps. */ - pruning_steps = gen_partprune_steps(subpart, partprunequal, + /* + * Convert pruning qual to pruning steps. Since these steps will be + * executed during execution, we can pass 'forplanner' as false to + * allow steps to be generated that are unsafe for evaluation during + * planning, e.g. evaluation of stable functions. + */ + pruning_steps = gen_partprune_steps(subpart, partprunequal, false, &contradictory); if (contradictory) @@ -523,18 +531,22 @@ 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". + * + * 'forplanner' must be true when being called from the query planner. * * 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, List *clauses, bool forplanner, + bool *contradictory) { GeneratePruningStepsContext context; context.next_step_id = 0; context.steps = NIL; + context.forplanner = forplanner; /* The clauses list may be modified below, so better make a copy. */ clauses = list_copy(clauses); @@ -574,9 +586,11 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns indexes into rel->part_rels of the minimum set of child - * partitions which must be scanned to satisfy rel's baserestrictinfo - * quals. + * Process rel's baserestrictinfo and make use of quals which can be + * evaluated during query planning in order to determine the minimum set + * of partitions which must be scanned to satisfy these quals. Returns + * the matching partitions in the form of a Bitmapset containing the + * indexes into rel's part_rels array. * * Callers must ensure that 'rel' is a partitioned table. */ @@ -603,9 +617,12 @@ prune_append_rel_partitions(RelOptInfo *rel) /* * Process clauses. If the clauses are found to be contradictory, we can - * return the empty set. + * return the empty set. Pass 'forplanner' as true to indicate to the + * pruning code that we only want pruning steps that can be evaluated + * during planning. */ - pruning_steps = gen_partprune_steps(rel, clauses, &contradictory); + pruning_steps = gen_partprune_steps(rel, clauses, true, + &contradictory); if (contradictory) return NULL; @@ -1646,18 +1663,48 @@ match_clause_to_partition_key(RelOptInfo *rel, /* * Matched with this key. Now check various properties of the clause - * to see if it's sane to use it for pruning. In most of these cases, - * we can return UNSUPPORTED because the same failure would occur no - * matter which partkey it's matched to. + * to see if it's sane to use it for pruning. This sometimes depends + * on if we're generating pruning steps for evaluation in the planner + * or in the executor. The planner is unable to evaluate any stable + * functions, for example. In most of these cases, we can return + * UNSUPPORTED because the same failure would occur no matter which + * partkey it's matched to. */ - /* - * 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; + if (context->forplanner) + { + /* + * 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, to which we must reject. + */ + if (!IsA(expr, Const)) + return PARTCLAUSE_UNSUPPORTED; + + /* + * We must reject comparing to consts if the comparison function + * itself is not immutable. (This is the case for most of the + * built-in timestamptz operators.) + */ + if (op_volatile(opno) != PROVOLATILE_IMMUTABLE) + 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 +1713,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. * @@ -2961,15 +3004,18 @@ analyze_partkey_exprs(PartitionedRelPruneInfo *pinfo, List *steps, { PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc); ListCell *lc2; + ListCell *lc3; int keyno; if (!IsA(step, PartitionPruneStepOp)) continue; keyno = 0; - foreach(lc2, step->exprs) + forboth(lc2, step->exprs, lc3, step->cmpfns) { Expr *expr = lfirst(lc2); + Oid fnoid = lfirst_oid(lc3); + if (!IsA(expr, Const)) { @@ -2992,6 +3038,18 @@ analyze_partkey_exprs(PartitionedRelPruneInfo *pinfo, List *steps, doruntimeprune = true; } + + /* + * Pruning done during planning won't have pruned using quals with + * a non-immutable comparison functions, so we'll switch on + * run-time pruning for these. + */ + else if (func_volatile(fnoid) != PROVOLATILE_IMMUTABLE) + { + /* No need to check for exec params inside a const */ + doruntimeprune = true; + pinfo->do_initial_prune = true; + } keyno++; } } diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index bd64bed8fc..b30797434f 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -3036,6 +3036,24 @@ select * from listp where a = (select null::int); (7 rows) drop table listp; +-- +-- check that stable query clauses are only used in run-time pruning +-- +create table stable_qual_pruning (a timestamp) partition by range (a); +create table stable_qual_pruning1 partition of stable_qual_pruning for values from ('2019-01-01') to ('2019-02-01'); +create table stable_qual_pruning2 partition of stable_qual_pruning for values from ('2019-02-01') to ('2019-03-01'); +-- timestamp < timestamptz comparison is only stable, not immutable +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning where a < '2019-02-01'::timestamptz; + QUERY PLAN +-------------------------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 1 + -> Seq Scan on stable_qual_pruning1 (actual rows=0 loops=1) + Filter: (a < 'Fri Feb 01 00:00:00 2019 PST'::timestamp with time zone) +(4 rows) + +drop table stable_qual_pruning; -- 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 246c6274af..9e10819be0 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -754,6 +754,18 @@ select * from listp where a = (select null::int); drop table listp; +-- +-- check that stable query clauses are only used in run-time pruning +-- +create table stable_qual_pruning (a timestamp) partition by range (a); +create table stable_qual_pruning1 partition of stable_qual_pruning for values from ('2019-01-01') to ('2019-02-01'); +create table stable_qual_pruning2 partition of stable_qual_pruning for values from ('2019-02-01') to ('2019-03-01'); +-- timestamp < timestamptz comparison is only stable, not immutable +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning where a < '2019-02-01'::timestamptz; + +drop table stable_qual_pruning; + -- Ensure runtime pruning works with initplans params with boolean types create table boolvalues (value bool not null); insert into boolvalues values('t'),('f');