Re: inconsistent results querying table partitioned by date - Mailing list pgsql-bugs

From Tom Lane
Subject Re: inconsistent results querying table partitioned by date
Date
Msg-id 14101.1558128611@sss.pgh.pa.us
Whole thread Raw
In response to Re: inconsistent results querying table partitioned by date  (Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>)
Responses Re: inconsistent results querying table partitioned by date  (David Rowley <david.rowley@2ndquadrant.com>)
List pgsql-bugs
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> writes:
> On 2019/05/17 7:50, Tom Lane wrote:
>> Also, it seems like we could save at least some of the planning expense by
>> having the PARTCLAUSE_INITIAL pass report back whether it had discarded
>> any Param-bearing clauses or not.  If not, there's definitely no need to
>> run the PARTCLAUSE_EXEC pass.

> That sounds like a good idea.

Here's a patch that does it like that.  Since this requires additional
output values from gen_partprune_steps, I decided to go "all in" on
having GeneratePruningStepsContext contain everything of interest,
including all these result flags.  That makes the patch a bit more
invasive than it could perhaps be otherwise, but I think it's cleaner.
And these are just internal APIs in partprune.c so there's no
compatibility problems to worry about.

It's kind of hard to tell what this code is actually doing, due to
the lack of visibility in EXPLAIN output (something I think Robert
has kvetched about before).  To try to check that, I made the very
quick-and-dirty explain.c patches below, which I'm *not* proposing
to commit.  They showed that this code is making the same decisions
as HEAD for all the currently-existing test cases, which is probably
what we want it to do.  The new test cases added by the main patch
show up as having different initial and exec step lists, which is
also expected.  So I feel pretty good that this is acting as we wish.

The fly in the ointment is what about back-patching?  After looking
around, I think that we can probably get away with changing the run-time
data structures (PartitionPruneContext, PartitionedRelPruningData,
PartitionPruningData) in v11; it seems unlikely that anything outside
partprune.c and execPartition.c would have occasion to touch those.
But it's harder to argue that for the node type PartitionedRelPruneInfo,
since that's part of plan trees.  What I suggest we do for v11 is to
leave the existing fields in that node type that're removed by this
patch present, but unused, and add initial_pruning_steps and
exec_pruning_steps at the end.  This would confuse any code that's
studying the contents of PartitionedRelPruneInfo closely, but
probably as long as we set do_initial_prune and do_exec_prune properly,
that would satisfy most code that might be inspecting it.

Since time is growing short before beta1 wrap, I'm going to go ahead
and push this in hopes of getting buildfarm cycles on it.  But feel
free to review and look for further improvements.  There's plenty
of time to rethink the data-structure details for the v11 backpatch,
also.

            regards, tom lane

diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 70709e5..d663118 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -183,6 +183,11 @@ static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
                                      bool *isnull,
                                      int maxfieldlen);
 static List *adjust_partition_tlist(List *tlist, TupleConversionMap *map);
+static void ExecInitPruningContext(PartitionPruneContext *context,
+                       List *pruning_steps,
+                       PartitionDesc partdesc,
+                       PartitionKey partkey,
+                       PlanState *planstate);
 static void find_matching_subplans_recurse(PartitionPruningData *prunedata,
                                PartitionedRelPruningData *pprune,
                                bool initial_prune,
@@ -1614,13 +1619,9 @@ ExecCreatePartitionPruneState(PlanState *planstate,
         {
             PartitionedRelPruneInfo *pinfo = lfirst_node(PartitionedRelPruneInfo, lc2);
             PartitionedRelPruningData *pprune = &prunedata->partrelprunedata[j];
-            PartitionPruneContext *context = &pprune->context;
             Relation    partrel;
             PartitionDesc partdesc;
             PartitionKey partkey;
-            int            partnatts;
-            int            n_steps;
-            ListCell   *lc3;

             /* present_parts is also subject to later modification */
             pprune->present_parts = bms_copy(pinfo->present_parts);
@@ -1700,66 +1701,36 @@ ExecCreatePartitionPruneState(PlanState *planstate,
                 Assert(pd_idx == pinfo->nparts);
             }

-            n_steps = list_length(pinfo->pruning_steps);
-
-            context->strategy = partkey->strategy;
-            context->partnatts = partnatts = partkey->partnatts;
-            context->nparts = pinfo->nparts;
-            context->boundinfo = partdesc->boundinfo;
-            context->partcollation = partkey->partcollation;
-            context->partsupfunc = partkey->partsupfunc;
-
-            /* We'll look up type-specific support functions as needed */
-            context->stepcmpfuncs = (FmgrInfo *)
-                palloc0(sizeof(FmgrInfo) * n_steps * partnatts);
-
-            context->ppccontext = CurrentMemoryContext;
-            context->planstate = planstate;
-
-            /* Initialize expression state for each expression we need */
-            context->exprstates = (ExprState **)
-                palloc0(sizeof(ExprState *) * n_steps * partnatts);
-            foreach(lc3, pinfo->pruning_steps)
+            /*
+             * Initialize pruning contexts as needed.
+             */
+            pprune->initial_pruning_steps = pinfo->initial_pruning_steps;
+            if (pinfo->initial_pruning_steps)
             {
-                PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc3);
-                ListCell   *lc4;
-                int            keyno;
-
-                /* not needed for other step kinds */
-                if (!IsA(step, PartitionPruneStepOp))
-                    continue;
-
-                Assert(list_length(step->exprs) <= partnatts);
-
-                keyno = 0;
-                foreach(lc4, step->exprs)
-                {
-                    Expr       *expr = (Expr *) lfirst(lc4);
-
-                    /* not needed for Consts */
-                    if (!IsA(expr, Const))
-                    {
-                        int            stateidx = PruneCxtStateIdx(partnatts,
-                                                                step->step.step_id,
-                                                                keyno);
-
-                        context->exprstates[stateidx] =
-                            ExecInitExpr(expr, context->planstate);
-                    }
-                    keyno++;
-                }
+                ExecInitPruningContext(&pprune->initial_context,
+                                       pinfo->initial_pruning_steps,
+                                       partdesc, partkey, planstate);
+                /* Record whether initial pruning is needed at any level */
+                prunestate->do_initial_prune = true;
+            }
+            else
+            {
+                /*
+                 * Hack: ExecFindInitialMatchingSubPlans requires this field
+                 * to be valid whether pruning or not.
+                 */
+                pprune->initial_context.nparts = partdesc->nparts;
             }

-            /* Array is not modified at runtime, so just point to plan's copy */
-            context->exprhasexecparam = pinfo->hasexecparam;
-
-            pprune->pruning_steps = pinfo->pruning_steps;
-            pprune->do_initial_prune = pinfo->do_initial_prune;
-            pprune->do_exec_prune = pinfo->do_exec_prune;
-
-            /* Record if pruning would be useful at any level */
-            prunestate->do_initial_prune |= pinfo->do_initial_prune;
-            prunestate->do_exec_prune |= pinfo->do_exec_prune;
+            pprune->exec_pruning_steps = pinfo->exec_pruning_steps;
+            if (pinfo->exec_pruning_steps)
+            {
+                ExecInitPruningContext(&pprune->exec_context,
+                                       pinfo->exec_pruning_steps,
+                                       partdesc, partkey, planstate);
+                /* Record whether exec pruning is needed at any level */
+                prunestate->do_exec_prune = true;
+            }

             /*
              * Accumulate the IDs of all PARAM_EXEC Params affecting the
@@ -1777,6 +1748,71 @@ ExecCreatePartitionPruneState(PlanState *planstate,
 }

 /*
+ * Initialize a PartitionPruneContext for the given list of pruning steps.
+ */
+static void
+ExecInitPruningContext(PartitionPruneContext *context,
+                       List *pruning_steps,
+                       PartitionDesc partdesc,
+                       PartitionKey partkey,
+                       PlanState *planstate)
+{
+    int            n_steps;
+    int            partnatts;
+    ListCell   *lc;
+
+    n_steps = list_length(pruning_steps);
+
+    context->strategy = partkey->strategy;
+    context->partnatts = partnatts = partkey->partnatts;
+    context->nparts = partdesc->nparts;
+    context->boundinfo = partdesc->boundinfo;
+    context->partcollation = partkey->partcollation;
+    context->partsupfunc = partkey->partsupfunc;
+
+    /* We'll look up type-specific support functions as needed */
+    context->stepcmpfuncs = (FmgrInfo *)
+        palloc0(sizeof(FmgrInfo) * n_steps * partnatts);
+
+    context->ppccontext = CurrentMemoryContext;
+    context->planstate = planstate;
+
+    /* Initialize expression state for each expression we need */
+    context->exprstates = (ExprState **)
+        palloc0(sizeof(ExprState *) * n_steps * partnatts);
+    foreach(lc, pruning_steps)
+    {
+        PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc);
+        ListCell   *lc2;
+        int            keyno;
+
+        /* not needed for other step kinds */
+        if (!IsA(step, PartitionPruneStepOp))
+            continue;
+
+        Assert(list_length(step->exprs) <= partnatts);
+
+        keyno = 0;
+        foreach(lc2, step->exprs)
+        {
+            Expr       *expr = (Expr *) lfirst(lc2);
+
+            /* not needed for Consts */
+            if (!IsA(expr, Const))
+            {
+                int            stateidx = PruneCxtStateIdx(partnatts,
+                                                        step->step.step_id,
+                                                        keyno);
+
+                context->exprstates[stateidx] =
+                    ExecInitExpr(expr, context->planstate);
+            }
+            keyno++;
+        }
+    }
+}
+
+/*
  * ExecFindInitialMatchingSubPlans
  *        Identify the set of subplans that cannot be eliminated by initial
  *        pruning, disregarding any pruning constraints involving PARAM_EXEC
@@ -1824,7 +1860,8 @@ ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubplans)
         find_matching_subplans_recurse(prunedata, pprune, true, &result);

         /* Expression eval may have used space in node's ps_ExprContext too */
-        ResetExprContext(pprune->context.planstate->ps_ExprContext);
+        if (pprune->initial_pruning_steps)
+            ResetExprContext(pprune->initial_context.planstate->ps_ExprContext);
     }

     /* Add in any subplans that partition pruning didn't account for */
@@ -1888,7 +1925,7 @@ ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubplans)
             for (j = prunedata->num_partrelprunedata - 1; j >= 0; j--)
             {
                 PartitionedRelPruningData *pprune = &prunedata->partrelprunedata[j];
-                int            nparts = pprune->context.nparts;
+                int            nparts = pprune->initial_context.nparts;
                 int            k;

                 /* We just rebuild present_parts from scratch */
@@ -1993,7 +2030,8 @@ ExecFindMatchingSubPlans(PartitionPruneState *prunestate)
         find_matching_subplans_recurse(prunedata, pprune, false, &result);

         /* Expression eval may have used space in node's ps_ExprContext too */
-        ResetExprContext(pprune->context.planstate->ps_ExprContext);
+        if (pprune->exec_pruning_steps)
+            ResetExprContext(pprune->exec_context.planstate->ps_ExprContext);
     }

     /* Add in any subplans that partition pruning didn't account for */
@@ -2029,15 +2067,15 @@ find_matching_subplans_recurse(PartitionPruningData *prunedata,
     check_stack_depth();

     /* Only prune if pruning would be useful at this level. */
-    if (initial_prune ? pprune->do_initial_prune : pprune->do_exec_prune)
+    if (initial_prune && pprune->initial_pruning_steps)
     {
-        PartitionPruneContext *context = &pprune->context;
-
-        /* Set whether we can evaluate PARAM_EXEC Params or not */
-        context->evalexecparams = !initial_prune;
-
-        partset = get_matching_partitions(context,
-                                          pprune->pruning_steps);
+        partset = get_matching_partitions(&pprune->initial_context,
+                                          pprune->initial_pruning_steps);
+    }
+    else if (!initial_prune && pprune->exec_pruning_steps)
+    {
+        partset = get_matching_partitions(&pprune->exec_context,
+                                          pprune->exec_pruning_steps);
     }
     else
     {
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 780d7ab..78deade 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1198,16 +1198,13 @@ _copyPartitionedRelPruneInfo(const PartitionedRelPruneInfo *from)
     PartitionedRelPruneInfo *newnode = makeNode(PartitionedRelPruneInfo);

     COPY_SCALAR_FIELD(rtindex);
-    COPY_NODE_FIELD(pruning_steps);
     COPY_BITMAPSET_FIELD(present_parts);
     COPY_SCALAR_FIELD(nparts);
-    COPY_SCALAR_FIELD(nexprs);
     COPY_POINTER_FIELD(subplan_map, from->nparts * sizeof(int));
     COPY_POINTER_FIELD(subpart_map, from->nparts * sizeof(int));
     COPY_POINTER_FIELD(relid_map, from->nparts * sizeof(Oid));
-    COPY_POINTER_FIELD(hasexecparam, from->nexprs * sizeof(bool));
-    COPY_SCALAR_FIELD(do_initial_prune);
-    COPY_SCALAR_FIELD(do_exec_prune);
+    COPY_NODE_FIELD(initial_pruning_steps);
+    COPY_NODE_FIELD(exec_pruning_steps);
     COPY_BITMAPSET_FIELD(execparamids);

     return newnode;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 387e4b9..237598e 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -948,16 +948,13 @@ _outPartitionedRelPruneInfo(StringInfo str, const PartitionedRelPruneInfo *node)
     WRITE_NODE_TYPE("PARTITIONEDRELPRUNEINFO");

     WRITE_UINT_FIELD(rtindex);
-    WRITE_NODE_FIELD(pruning_steps);
     WRITE_BITMAPSET_FIELD(present_parts);
     WRITE_INT_FIELD(nparts);
-    WRITE_INT_FIELD(nexprs);
     WRITE_INT_ARRAY(subplan_map, node->nparts);
     WRITE_INT_ARRAY(subpart_map, node->nparts);
     WRITE_OID_ARRAY(relid_map, node->nparts);
-    WRITE_BOOL_ARRAY(hasexecparam, node->nexprs);
-    WRITE_BOOL_FIELD(do_initial_prune);
-    WRITE_BOOL_FIELD(do_exec_prune);
+    WRITE_NODE_FIELD(initial_pruning_steps);
+    WRITE_NODE_FIELD(exec_pruning_steps);
     WRITE_BITMAPSET_FIELD(execparamids);
 }

diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3b96492..6c2626e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2388,16 +2388,13 @@ _readPartitionedRelPruneInfo(void)
     READ_LOCALS(PartitionedRelPruneInfo);

     READ_UINT_FIELD(rtindex);
-    READ_NODE_FIELD(pruning_steps);
     READ_BITMAPSET_FIELD(present_parts);
     READ_INT_FIELD(nparts);
-    READ_INT_FIELD(nexprs);
     READ_INT_ARRAY(subplan_map, local_node->nparts);
     READ_INT_ARRAY(subpart_map, local_node->nparts);
     READ_OID_ARRAY(relid_map, local_node->nparts);
-    READ_BOOL_ARRAY(hasexecparam, local_node->nexprs);
-    READ_BOOL_FIELD(do_initial_prune);
-    READ_BOOL_FIELD(do_exec_prune);
+    READ_NODE_FIELD(initial_pruning_steps);
+    READ_NODE_FIELD(exec_pruning_steps);
     READ_BITMAPSET_FIELD(execparamids);

     READ_DONE();
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index ff3caf1..53f814f 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -85,21 +85,42 @@ typedef enum PartClauseMatchStatus
 } PartClauseMatchStatus;

 /*
+ * PartClauseTarget
+ *        Identifies which qual clauses we can use for generating pruning steps
+ */
+typedef enum PartClauseTarget
+{
+    PARTTARGET_PLANNER,            /* want to prune during planning */
+    PARTTARGET_INITIAL,            /* want to prune during executor startup */
+    PARTTARGET_EXEC                /* want to prune during each plan node scan */
+} PartClauseTarget;
+
+/*
  * GeneratePruningStepsContext
  *        Information about the current state of generation of "pruning steps"
  *        for a given set of clauses
  *
- * gen_partprune_steps() initializes an instance of this struct, which is used
- * throughout the step generation process.
+ * gen_partprune_steps() initializes and returns an instance of this struct.
+ *
+ * Note that has_mutable_op, has_mutable_arg, and has_exec_param are set if
+ * we found any potentially-useful-for-pruning clause having those properties,
+ * whether or not we actually used the clause in the steps list.  This
+ * definition allows us to skip the PARTTARGET_EXEC pass in some cases.
  */
 typedef struct GeneratePruningStepsContext
 {
-    /* Input data: */
-    bool        forplanner;        /* true when generating steps to be used
-                                 * during query planning */
-    /* Working state and result data: */
+    /* Copies of input arguments for gen_partprune_steps: */
+    RelOptInfo *rel;            /* the partitioned relation */
+    PartClauseTarget target;    /* use-case we're generating steps for */
+    /* Result data: */
+    List       *steps;            /* list of PartitionPruneSteps */
+    bool        has_mutable_op; /* clauses include any stable operators */
+    bool        has_mutable_arg;    /* clauses include any mutable comparison
+                                     * values, *other than* exec params */
+    bool        has_exec_param; /* clauses include any PARAM_EXEC params */
+    bool        contradictory;    /* clauses were proven self-contradictory */
+    /* Working state: */
     int            next_step_id;
-    List       *steps;            /* output, list of PartitionPruneSteps */
 } GeneratePruningStepsContext;

 /* The result of performing one PartitionPruneStep */
@@ -121,22 +142,20 @@ 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 forplanner, bool *contradictory);
+static void gen_partprune_steps(RelOptInfo *rel, List *clauses,
+                    PartClauseTarget target,
+                    GeneratePruningStepsContext *context);
 static List *gen_partprune_steps_internal(GeneratePruningStepsContext *context,
-                             RelOptInfo *rel, List *clauses,
-                             bool *contradictory);
+                             List *clauses);
 static PartitionPruneStep *gen_prune_step_op(GeneratePruningStepsContext *context,
                   StrategyNumber opstrategy, bool op_is_ne,
                   List *exprs, List *cmpfns, Bitmapset *nullkeys);
 static PartitionPruneStep *gen_prune_step_combine(GeneratePruningStepsContext *context,
                        List *source_stepids,
                        PartitionPruneCombineOp combineOp);
-static PartitionPruneStep *gen_prune_steps_from_opexps(PartitionScheme part_scheme,
-                            GeneratePruningStepsContext *context,
+static PartitionPruneStep *gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
                             List **keyclauses, Bitmapset *nullkeys);
-static PartClauseMatchStatus match_clause_to_partition_key(RelOptInfo *rel,
-                              GeneratePruningStepsContext *context,
+static PartClauseMatchStatus match_clause_to_partition_key(GeneratePruningStepsContext *context,
                               Expr *clause, Expr *partkey, int partkeyidx,
                               bool *clause_is_not_null,
                               PartClauseInfo **pc, List **clause_steps);
@@ -169,8 +188,7 @@ static PruneStepResult *get_matching_range_bounds(PartitionPruneContext *context
                           FmgrInfo *partsupfunc, Bitmapset *nullkeys);
 static Bitmapset *pull_exec_paramids(Expr *expr);
 static bool pull_exec_paramids_walker(Node *node, Bitmapset **context);
-static bool analyze_partkey_exprs(PartitionedRelPruneInfo *pinfo, List *steps,
-                      int partnatts);
+static Bitmapset *get_partkey_exec_paramids(List *steps);
 static PruneStepResult *perform_pruning_base_step(PartitionPruneContext *context,
                           PartitionPruneStepOp *opstep);
 static PruneStepResult *perform_pruning_combine_step(PartitionPruneContext *context,
@@ -178,7 +196,7 @@ static PruneStepResult *perform_pruning_combine_step(PartitionPruneContext *cont
                              PruneStepResult **step_results);
 static bool match_boolean_partition_clause(Oid partopfamily, Expr *clause,
                                Expr *partkey, Expr **outconst);
-static bool partkey_datum_from_expr(PartitionPruneContext *context,
+static void partkey_datum_from_expr(PartitionPruneContext *context,
                         Expr *expr, int stateidx,
                         Datum *value, bool *isnull);

@@ -347,10 +365,11 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
         Index        rti = lfirst_int(lc);
         RelOptInfo *subpart = find_base_rel(root, rti);
         PartitionedRelPruneInfo *pinfo;
-        int            partnatts = subpart->part_scheme->partnatts;
         List       *partprunequal;
-        List       *pruning_steps;
-        bool        contradictory;
+        List       *initial_pruning_steps;
+        List       *exec_pruning_steps;
+        Bitmapset  *execparamids;
+        GeneratePruningStepsContext context;

         /*
          * Fill the mapping array.
@@ -415,15 +434,16 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
         }

         /*
-         * Convert pruning qual to pruning steps.  Since these steps will be
-         * used in the executor, 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.
+         * Convert pruning qual to pruning steps.  We may need to do this
+         * twice, once to obtain executor startup pruning steps, and once for
+         * executor per-scan pruning steps.  This first pass creates startup
+         * pruning steps and detects whether there's any possibly-useful quals
+         * that would require per-scan pruning.
          */
-        pruning_steps = gen_partprune_steps(subpart, partprunequal, false,
-                                            &contradictory);
+        gen_partprune_steps(subpart, partprunequal, PARTTARGET_INITIAL,
+                            &context);

-        if (contradictory)
+        if (context.contradictory)
         {
             /*
              * This shouldn't happen as the planner should have detected this
@@ -437,20 +457,63 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
             return NIL;
         }

+        /*
+         * If no mutable operators or expressions appear in usable pruning
+         * clauses, then there's no point in running startup pruning, because
+         * plan-time pruning should have pruned everything prunable.
+         */
+        if (context.has_mutable_op || context.has_mutable_arg)
+            initial_pruning_steps = context.steps;
+        else
+            initial_pruning_steps = NIL;
+
+        /*
+         * If no exec Params appear in potentially-usable pruning clauses,
+         * then there's no point in even thinking about per-scan pruning.
+         */
+        if (context.has_exec_param)
+        {
+            /* ... OK, we'd better think about it */
+            gen_partprune_steps(subpart, partprunequal, PARTTARGET_EXEC,
+                                &context);
+
+            if (context.contradictory)
+            {
+                /* As above, skip run-time pruning if anything fishy happens */
+                return NIL;
+            }
+
+            exec_pruning_steps = context.steps;
+
+            /*
+             * Detect which exec Params actually got used; the fact that some
+             * were in available clauses doesn't mean we actually used them.
+             * Skip per-scan pruning if there are none.
+             */
+            execparamids = get_partkey_exec_paramids(exec_pruning_steps);
+
+            if (bms_is_empty(execparamids))
+                exec_pruning_steps = NIL;
+        }
+        else
+        {
+            /* No exec Params anywhere, so forget about scan-time pruning */
+            exec_pruning_steps = NIL;
+            execparamids = NULL;
+        }
+
+        if (initial_pruning_steps || exec_pruning_steps)
+            doruntimeprune = true;
+
         /* Begin constructing the PartitionedRelPruneInfo for this rel */
         pinfo = makeNode(PartitionedRelPruneInfo);
         pinfo->rtindex = rti;
-        pinfo->pruning_steps = pruning_steps;
+        pinfo->initial_pruning_steps = initial_pruning_steps;
+        pinfo->exec_pruning_steps = exec_pruning_steps;
+        pinfo->execparamids = execparamids;
         /* Remaining fields will be filled in the next loop */

         pinfolist = lappend(pinfolist, pinfo);
-
-        /*
-         * Determine which pruning types should be enabled at this level. This
-         * also records paramids relevant to pruning steps in 'pinfo'.
-         */
-        doruntimeprune |= analyze_partkey_exprs(pinfo, pruning_steps,
-                                                partnatts);
     }

     if (!doruntimeprune)
@@ -532,37 +595,25 @@ 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".
+ *        Process 'clauses' (typically a rel's baserestrictinfo list of clauses)
+ *        and create a list of "partition pruning steps".
  *
- * 'forplanner' must be true when generating steps to be evaluated during
- * query planning, false when generating steps to be used at run-time.
+ * 'target' tells whether to generate pruning steps for planning (use
+ * immutable clauses only), or for executor startup (use any allowable
+ * clause except ones containing PARAM_EXEC Params), or for executor
+ * per-scan pruning (use any allowable clause).
  *
- * The result generated with forplanner=false includes all clauses that
- * are selected with forplanner=true, because in some cases we need a
- * combination of clauses to prune successfully.  For example, if we
- * are partitioning on a hash of columns A and B, and we have clauses
- * "WHERE A=constant AND B=nonconstant", we can't do anything at plan
- * time even though the first clause would be evaluable then.  And we
- * must include the first clause when called with forplanner=false,
- * or we'll fail to prune at run-time either.  This does mean that when
- * called with forplanner=false, we may return steps that don't actually
- * need to be executed at runtime; it's left to analyze_partkey_exprs()
- * to (re)discover that.
- *
- * If the clauses in the input list are contradictory or there is a
- * pseudo-constant "false", *contradictory is set to true upon return,
- * else it's set false.
+ * 'context' is an output argument that receives the steps list as well as
+ * some subsidiary flags; see the GeneratePruningStepsContext typedef.
  */
-static List *
-gen_partprune_steps(RelOptInfo *rel, List *clauses, bool forplanner,
-                    bool *contradictory)
+static void
+gen_partprune_steps(RelOptInfo *rel, List *clauses, PartClauseTarget target,
+                    GeneratePruningStepsContext *context)
 {
-    GeneratePruningStepsContext context;
-
-    context.forplanner = forplanner;
-    context.next_step_id = 0;
-    context.steps = NIL;
+    /* Initialize all output values to zero/false/NULL */
+    memset(context, 0, sizeof(GeneratePruningStepsContext));
+    context->rel = rel;
+    context->target = target;

     /*
      * For sub-partitioned tables there's a corner case where if the
@@ -593,9 +644,7 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool forplanner,
     }

     /* Down into the rabbit-hole. */
-    (void) gen_partprune_steps_internal(&context, rel, clauses, contradictory);
-
-    return context.steps;
+    (void) gen_partprune_steps_internal(context, clauses);
 }

 /*
@@ -613,7 +662,7 @@ prune_append_rel_partitions(RelOptInfo *rel)
 {
     List       *clauses = rel->baserestrictinfo;
     List       *pruning_steps;
-    bool        contradictory;
+    GeneratePruningStepsContext gcontext;
     PartitionPruneContext context;

     Assert(rel->part_scheme != NULL);
@@ -630,15 +679,19 @@ prune_append_rel_partitions(RelOptInfo *rel)
         return bms_add_range(NULL, 0, rel->nparts - 1);

     /*
-     * Process clauses.  If the clauses are found to be contradictory, we can
-     * 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.
+     * Process clauses to extract pruning steps that are usable at plan time.
+     * If the clauses are found to be contradictory, we can return the empty
+     * set.
      */
-    pruning_steps = gen_partprune_steps(rel, clauses, true,
-                                        &contradictory);
-    if (contradictory)
+    gen_partprune_steps(rel, clauses, PARTTARGET_PLANNER,
+                        &gcontext);
+    if (gcontext.contradictory)
         return NULL;
+    pruning_steps = gcontext.steps;
+
+    /* If there's nothing usable, return all partitions */
+    if (pruning_steps == NIL)
+        return bms_add_range(NULL, 0, rel->nparts - 1);

     /* Set up PartitionPruneContext */
     context.strategy = rel->part_scheme->strategy;
@@ -655,8 +708,6 @@ prune_append_rel_partitions(RelOptInfo *rel)
     /* These are not valid when being called from the planner */
     context.planstate = NULL;
     context.exprstates = NULL;
-    context.exprhasexecparam = NULL;
-    context.evalexecparams = false;

     /* Actual pruning happens here. */
     return get_matching_partitions(&context, pruning_steps);
@@ -667,7 +718,7 @@ prune_append_rel_partitions(RelOptInfo *rel)
  *        Determine partitions that survive partition pruning
  *
  * Note: context->planstate must be set to a valid PlanState when the
- * pruning_steps were generated with 'forplanner' = false.
+ * pruning_steps were generated with a target other than PARTTARGET_PLANNER.
  *
  * Returns a Bitmapset of the RelOptInfo->part_rels indexes of the surviving
  * partitions.
@@ -786,16 +837,15 @@ get_matching_partitions(PartitionPruneContext *context, List *pruning_steps)
  * even across recursive calls.
  *
  * If we find clauses that are mutually contradictory, or a pseudoconstant
- * clause that contains false, we set *contradictory to true and return NIL
- * (that is, no pruning steps).  Caller should consider all partitions as
- * pruned in that case.  Otherwise, *contradictory is set to false.
+ * clause that contains false, we set context->contradictory to true and
+ * return NIL (that is, no pruning steps).  Caller should consider all
+ * partitions as pruned in that case.
  */
 static List *
 gen_partprune_steps_internal(GeneratePruningStepsContext *context,
-                             RelOptInfo *rel, List *clauses,
-                             bool *contradictory)
+                             List *clauses)
 {
-    PartitionScheme part_scheme = rel->part_scheme;
+    PartitionScheme part_scheme = context->rel->part_scheme;
     List       *keyclauses[PARTITION_MAX_KEYS];
     Bitmapset  *nullkeys = NULL,
                *notnullkeys = NULL;
@@ -803,8 +853,6 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
     List       *result = NIL;
     ListCell   *lc;

-    *contradictory = false;
-
     memset(keyclauses, 0, sizeof(keyclauses));
     foreach(lc, clauses)
     {
@@ -820,7 +868,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
             (((Const *) clause)->constisnull ||
              !DatumGetBool(((Const *) clause)->constvalue)))
         {
-            *contradictory = true;
+            context->contradictory = true;
             return NIL;
         }

@@ -842,6 +890,12 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
                 ListCell   *lc1;

                 /*
+                 * We can share the outer context area with the recursive
+                 * call, but contradictory had better not be true yet.
+                 */
+                Assert(!context->contradictory);
+
+                /*
                  * Get pruning step for each arg.  If we get contradictory for
                  * all args, it means the OR expression is false as a whole.
                  */
@@ -851,11 +905,18 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
                     bool        arg_contradictory;
                     List       *argsteps;

-                    argsteps =
-                        gen_partprune_steps_internal(context, rel,
-                                                     list_make1(arg),
-                                                     &arg_contradictory);
-                    if (!arg_contradictory)
+                    argsteps = gen_partprune_steps_internal(context,
+                                                            list_make1(arg));
+                    arg_contradictory = context->contradictory;
+                    /* Keep context->contradictory clear till we're done */
+                    context->contradictory = false;
+
+                    if (arg_contradictory)
+                    {
+                        /* Just ignore self-contradictory arguments. */
+                        continue;
+                    }
+                    else
                         all_args_contradictory = false;

                     if (argsteps != NIL)
@@ -869,34 +930,28 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
                     else
                     {
                         /*
-                         * No steps either means that arg_contradictory is
-                         * true or the arg didn't contain a clause matching
-                         * this partition key.
+                         * The arg didn't contain a clause matching this
+                         * partition key.  We cannot prune using such an arg.
+                         * To indicate that to the pruning code, we must
+                         * construct a dummy PartitionPruneStepCombine whose
+                         * source_stepids is set to an empty List.
                          *
-                         * In case of the latter, we cannot prune using such
-                         * an arg.  To indicate that to the pruning code, we
-                         * must construct a dummy PartitionPruneStepCombine
-                         * whose source_stepids is set to an empty List.
                          * However, if we can prove using constraint exclusion
                          * that the clause refutes the table's partition
                          * constraint (if it's sub-partitioned), we need not
                          * bother with that.  That is, we effectively ignore
                          * this OR arm.
                          */
-                        List       *partconstr = rel->partition_qual;
+                        List       *partconstr = context->rel->partition_qual;
                         PartitionPruneStep *orstep;

-                        /* Just ignore this argument. */
-                        if (arg_contradictory)
-                            continue;
-
                         if (partconstr)
                         {
                             partconstr = (List *)
                                 expression_planner((Expr *) partconstr);
-                            if (rel->relid != 1)
+                            if (context->rel->relid != 1)
                                 ChangeVarNodes((Node *) partconstr, 1,
-                                               rel->relid, 0);
+                                               context->rel->relid, 0);
                             if (predicate_refuted_by(partconstr,
                                                      list_make1(arg),
                                                      false))
@@ -909,11 +964,12 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
                     }
                 }

-                *contradictory = all_args_contradictory;
-
-                /* Check if any contradicting clauses were found */
-                if (*contradictory)
+                /* If all the OR arms are contradictory, we can stop */
+                if (all_args_contradictory)
+                {
+                    context->contradictory = true;
                     return NIL;
+                }

                 if (arg_stepids != NIL)
                 {
@@ -937,9 +993,10 @@ 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,
-                                                        contradictory);
-                if (*contradictory)
+                argsteps = gen_partprune_steps_internal(context, args);
+
+                /* If any AND arm is contradictory, we can stop immediately */
+                if (context->contradictory)
                     return NIL;

                 foreach(lc1, argsteps)
@@ -969,17 +1026,16 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
         }

         /*
-         * Must be a clause for which we can check if one of its args matches
-         * the partition key.
+         * See if we can match this clause to any of the partition keys.
          */
         for (i = 0; i < part_scheme->partnatts; i++)
         {
-            Expr       *partkey = linitial(rel->partexprs[i]);
+            Expr       *partkey = linitial(context->rel->partexprs[i]);
             bool        clause_is_not_null = false;
             PartClauseInfo *pc = NULL;
             List       *clause_steps = NIL;

-            switch (match_clause_to_partition_key(rel, context,
+            switch (match_clause_to_partition_key(context,
                                                   clause, partkey, i,
                                                   &clause_is_not_null,
                                                   &pc, &clause_steps))
@@ -993,7 +1049,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
                      */
                     if (bms_is_member(i, nullkeys))
                     {
-                        *contradictory = true;
+                        context->contradictory = true;
                         return NIL;
                     }
                     generate_opsteps = true;
@@ -1006,7 +1062,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
                         /* check for conflicting IS NOT NULL */
                         if (bms_is_member(i, notnullkeys))
                         {
-                            *contradictory = true;
+                            context->contradictory = true;
                             return NIL;
                         }
                         nullkeys = bms_add_member(nullkeys, i);
@@ -1016,7 +1072,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
                         /* check for conflicting IS NULL */
                         if (bms_is_member(i, nullkeys))
                         {
-                            *contradictory = true;
+                            context->contradictory = true;
                             return NIL;
                         }
                         notnullkeys = bms_add_member(notnullkeys, i);
@@ -1030,7 +1086,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,

                 case PARTCLAUSE_MATCH_CONTRADICT:
                     /* We've nothing more to do if a contradiction was found. */
-                    *contradictory = true;
+                    context->contradictory = true;
                     return NIL;

                 case PARTCLAUSE_NOMATCH:
@@ -1091,8 +1147,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
         PartitionPruneStep *step;

         /* Strategy 2 */
-        step = gen_prune_steps_from_opexps(part_scheme, context,
-                                           keyclauses, nullkeys);
+        step = gen_prune_steps_from_opexps(context, keyclauses, nullkeys);
         if (step != NULL)
             result = lappend(result, step);
     }
@@ -1201,15 +1256,15 @@ gen_prune_step_combine(GeneratePruningStepsContext *context,
  * found for any subsequent keys; see specific notes below.
  */
 static PartitionPruneStep *
-gen_prune_steps_from_opexps(PartitionScheme part_scheme,
-                            GeneratePruningStepsContext *context,
+gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
                             List **keyclauses, Bitmapset *nullkeys)
 {
-    ListCell   *lc;
+    PartitionScheme part_scheme = context->rel->part_scheme;
     List       *opsteps = NIL;
     List       *btree_clauses[BTMaxStrategyNumber + 1],
                *hash_clauses[HTMaxStrategyNumber + 1];
     int            i;
+    ListCell   *lc;

     memset(btree_clauses, 0, sizeof(btree_clauses));
     memset(hash_clauses, 0, sizeof(hash_clauses));
@@ -1563,13 +1618,12 @@ gen_prune_steps_from_opexps(PartitionScheme part_scheme,
  *   Output arguments: none set.
  */
 static PartClauseMatchStatus
-match_clause_to_partition_key(RelOptInfo *rel,
-                              GeneratePruningStepsContext *context,
+match_clause_to_partition_key(GeneratePruningStepsContext *context,
                               Expr *clause, Expr *partkey, int partkeyidx,
                               bool *clause_is_not_null, PartClauseInfo **pc,
                               List **clause_steps)
 {
-    PartitionScheme part_scheme = rel->part_scheme;
+    PartitionScheme part_scheme = context->rel->part_scheme;
     Oid            partopfamily = part_scheme->partopfamily[partkeyidx],
                 partcoll = part_scheme->partcollation[partkeyidx];
     Expr       *expr;
@@ -1588,7 +1642,7 @@ match_clause_to_partition_key(RelOptInfo *rel,
         partclause->op_is_ne = false;
         partclause->expr = expr;
         /* We know that expr is of Boolean type. */
-        partclause->cmpfn = rel->part_scheme->partsupfunc[partkeyidx].fn_oid;
+        partclause->cmpfn = part_scheme->partsupfunc[partkeyidx].fn_oid;
         partclause->op_strategy = InvalidStrategy;

         *pc = partclause;
@@ -1647,59 +1701,6 @@ match_clause_to_partition_key(RelOptInfo *rel,
             return PARTCLAUSE_NOMATCH;

         /*
-         * 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.  (In particular, now that
-         * we've successfully matched one side of the opclause to a partkey,
-         * there is no chance that matching the other side to another partkey
-         * will produce a usable result, since that'd mean there are Vars on
-         * both sides.)
-         */
-        if (context->forplanner)
-        {
-            /*
-             * When pruning in the planner, we only support pruning using
-             * comparisons to constants.  Immutable subexpressions will have
-             * been folded to constants already, and we cannot prune on the
-             * basis of anything that's not immutable.
-             */
-            if (!IsA(expr, Const))
-                return PARTCLAUSE_UNSUPPORTED;
-
-            /*
-             * Also, the comparison operator itself must be immutable.
-             */
-            if (op_volatile(opno) != PROVOLATILE_IMMUTABLE)
-                return PARTCLAUSE_UNSUPPORTED;
-        }
-        else
-        {
-            /*
-             * Otherwise, non-consts are allowed, but we can't prune using an
-             * expression that contains Vars.
-             */
-            if (contain_var_clause((Node *) expr))
-                return PARTCLAUSE_UNSUPPORTED;
-
-            /*
-             * And we must reject anything containing a volatile function.
-             * Stable functions are OK though.  (We need not check this for
-             * the comparison operator itself: anything that belongs to a
-             * partitioning operator family must be at least stable.)
-             */
-            if (contain_volatile_functions((Node *) expr))
-                return PARTCLAUSE_UNSUPPORTED;
-        }
-
-        /*
-         * Only allow strict operators.  This will guarantee nulls are
-         * filtered.
-         */
-        if (!op_strict(opno))
-            return PARTCLAUSE_UNSUPPORTED;
-
-        /*
          * See if the operator is relevant to the partitioning opfamily.
          *
          * Normally we only care about operators that are listed as being part
@@ -1740,6 +1741,95 @@ match_clause_to_partition_key(RelOptInfo *rel,
         }

         /*
+         * Only allow strict operators.  This will guarantee nulls are
+         * filtered.  (This test is likely useless, since btree and hash
+         * comparison operators are generally strict.)
+         */
+        if (!op_strict(opno))
+            return PARTCLAUSE_UNSUPPORTED;
+
+        /*
+         * OK, we have a match to the partition key and a suitable operator.
+         * Examine the other argument to see if it's usable 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.  (In
+         * particular, now that we've successfully matched one side of the
+         * opclause to a partkey, there is no chance that matching the other
+         * side to another partkey will produce a usable result, since that'd
+         * mean there are Vars on both sides.)
+         *
+         * Also, if we reject an argument for a target-dependent reason, set
+         * appropriate fields of *context to report that.  We postpone these
+         * tests until after matching the partkey and the operator, so as to
+         * reduce the odds of setting the context fields for clauses that do
+         * not end up contributing to pruning steps.
+         *
+         * First, check for non-Const argument.  (We assume that any immutable
+         * subexpression will have been folded to a Const already.)
+         */
+        if (!IsA(expr, Const))
+        {
+            Bitmapset  *paramids;
+
+            /*
+             * When pruning in the planner, we only support pruning using
+             * comparisons to constants.  We cannot prune on the basis of
+             * anything that's not immutable.  (Note that has_mutable_arg and
+             * has_exec_param do not get set for this target value.)
+             */
+            if (context->target == PARTTARGET_PLANNER)
+                return PARTCLAUSE_UNSUPPORTED;
+
+            /*
+             * We can never prune using an expression that contains Vars.
+             */
+            if (contain_var_clause((Node *) expr))
+                return PARTCLAUSE_UNSUPPORTED;
+
+            /*
+             * And we must reject anything containing a volatile function.
+             * Stable functions are OK though.
+             */
+            if (contain_volatile_functions((Node *) expr))
+                return PARTCLAUSE_UNSUPPORTED;
+
+            /*
+             * See if there are any exec Params.  If so, we can only use this
+             * expression during per-scan pruning.
+             */
+            paramids = pull_exec_paramids(expr);
+            if (!bms_is_empty(paramids))
+            {
+                context->has_exec_param = true;
+                if (context->target != PARTTARGET_EXEC)
+                    return PARTCLAUSE_UNSUPPORTED;
+            }
+            else
+            {
+                /* It's potentially usable, but mutable */
+                context->has_mutable_arg = true;
+            }
+        }
+
+        /*
+         * Check whether the comparison operator itself is immutable.  (We
+         * assume anything that's in a btree or hash opclass is at least
+         * stable, but we need to check for immutability.)
+         */
+        if (op_volatile(opno) != PROVOLATILE_IMMUTABLE)
+        {
+            context->has_mutable_op = true;
+
+            /*
+             * When pruning in the planner, we cannot prune with mutable
+             * operators.
+             */
+            if (context->target == PARTTARGET_PLANNER)
+                return PARTCLAUSE_UNSUPPORTED;
+        }
+
+        /*
          * Now find the procedure to use, based on the types.  If the clause's
          * other argument is of the same type as the partitioning opclass's
          * declared input type, we can use the procedure cached in
@@ -1822,7 +1912,6 @@ match_clause_to_partition_key(RelOptInfo *rel,
         List       *elem_exprs,
                    *elem_clauses;
         ListCell   *lc1;
-        bool        contradictory;

         if (IsA(leftop, RelabelType))
             leftop = ((RelabelType *) leftop)->arg;
@@ -1833,54 +1922,8 @@ match_clause_to_partition_key(RelOptInfo *rel,
             return PARTCLAUSE_NOMATCH;

         /*
-         * Matched with this key.  Check various properties of the clause to
-         * see if it can sanely be used for partition pruning (this is
-         * identical to the logic for a plain OpExpr).
-         */
-        if (context->forplanner)
-        {
-            /*
-             * When pruning in the planner, we only support pruning using
-             * comparisons to constants.  Immutable subexpressions will have
-             * been folded to constants already, and we cannot prune on the
-             * basis of anything that's not immutable.
-             */
-            if (!IsA(rightop, Const))
-                return PARTCLAUSE_UNSUPPORTED;
-
-            /*
-             * Also, the comparison operator itself must be immutable.
-             */
-            if (op_volatile(saop_op) != PROVOLATILE_IMMUTABLE)
-                return PARTCLAUSE_UNSUPPORTED;
-        }
-        else
-        {
-            /*
-             * Otherwise, non-consts are allowed, but we can't prune using an
-             * expression that contains Vars.
-             */
-            if (contain_var_clause((Node *) rightop))
-                return PARTCLAUSE_UNSUPPORTED;
-
-            /*
-             * And we must reject anything containing a volatile function.
-             * Stable functions are OK though.  (We need not check this for
-             * the comparison operator itself: anything that belongs to a
-             * partitioning operator family must be at least stable.)
-             */
-            if (contain_volatile_functions((Node *) rightop))
-                return PARTCLAUSE_UNSUPPORTED;
-        }
-
-        /*
-         * Only allow strict operators.  This will guarantee nulls are
-         * filtered.
-         */
-        if (!op_strict(saop_op))
-            return PARTCLAUSE_UNSUPPORTED;
-
-        /*
+         * See if the operator is relevant to the partitioning opfamily.
+         *
          * In case of NOT IN (..), we get a '<>', which we handle if list
          * partitioning is in use and we're able to confirm that it's negator
          * is a btree equality operator belonging to the partitioning operator
@@ -1911,12 +1954,89 @@ match_clause_to_partition_key(RelOptInfo *rel,
         }

         /*
-         * First generate a list of Const nodes, one for each array element
-         * (excepting nulls).
+         * Only allow strict operators.  This will guarantee nulls are
+         * filtered.  (This test is likely useless, since btree and hash
+         * comparison operators are generally strict.)
+         */
+        if (!op_strict(saop_op))
+            return PARTCLAUSE_UNSUPPORTED;
+
+        /*
+         * OK, we have a match to the partition key and a suitable operator.
+         * Examine the array argument to see if it's usable for pruning.  This
+         * is identical to the logic for a plain OpExpr.
+         */
+        if (!IsA(rightop, Const))
+        {
+            Bitmapset  *paramids;
+
+            /*
+             * When pruning in the planner, we only support pruning using
+             * comparisons to constants.  We cannot prune on the basis of
+             * anything that's not immutable.  (Note that has_mutable_arg and
+             * has_exec_param do not get set for this target value.)
+             */
+            if (context->target == PARTTARGET_PLANNER)
+                return PARTCLAUSE_UNSUPPORTED;
+
+            /*
+             * We can never prune using an expression that contains Vars.
+             */
+            if (contain_var_clause((Node *) rightop))
+                return PARTCLAUSE_UNSUPPORTED;
+
+            /*
+             * And we must reject anything containing a volatile function.
+             * Stable functions are OK though.
+             */
+            if (contain_volatile_functions((Node *) rightop))
+                return PARTCLAUSE_UNSUPPORTED;
+
+            /*
+             * See if there are any exec Params.  If so, we can only use this
+             * expression during per-scan pruning.
+             */
+            paramids = pull_exec_paramids(rightop);
+            if (!bms_is_empty(paramids))
+            {
+                context->has_exec_param = true;
+                if (context->target != PARTTARGET_EXEC)
+                    return PARTCLAUSE_UNSUPPORTED;
+            }
+            else
+            {
+                /* It's potentially usable, but mutable */
+                context->has_mutable_arg = true;
+            }
+        }
+
+        /*
+         * Check whether the comparison operator itself is immutable.  (We
+         * assume anything that's in a btree or hash opclass is at least
+         * stable, but we need to check for immutability.)
+         */
+        if (op_volatile(saop_op) != PROVOLATILE_IMMUTABLE)
+        {
+            context->has_mutable_op = true;
+
+            /*
+             * When pruning in the planner, we cannot prune with mutable
+             * operators.
+             */
+            if (context->target == PARTTARGET_PLANNER)
+                return PARTCLAUSE_UNSUPPORTED;
+        }
+
+        /*
+         * Examine the contents of the array argument.
          */
         elem_exprs = NIL;
         if (IsA(rightop, Const))
         {
+            /*
+             * For a constant array, convert the elements to a list of Const
+             * nodes, one for each array element (excepting nulls).
+             */
             Const       *arr = (Const *) rightop;
             ArrayType  *arrval = DatumGetArrayTypeP(arr->constvalue);
             int16        elemlen;
@@ -1968,6 +2088,9 @@ match_clause_to_partition_key(RelOptInfo *rel,
             if (arrexpr->multidims)
                 return PARTCLAUSE_UNSUPPORTED;

+            /*
+             * Otherwise, we can just use the list of element values.
+             */
             elem_exprs = arrexpr->elements;
         }
         else
@@ -2000,10 +2123,8 @@ match_clause_to_partition_key(RelOptInfo *rel,
             elem_clauses = list_make1(makeBoolExpr(OR_EXPR, elem_clauses, -1));

         /* Finally, generate steps */
-        *clause_steps =
-            gen_partprune_steps_internal(context, rel, elem_clauses,
-                                         &contradictory);
-        if (contradictory)
+        *clause_steps = gen_partprune_steps_internal(context, elem_clauses);
+        if (context->contradictory)
             return PARTCLAUSE_MATCH_CONTRADICT;
         else if (*clause_steps == NIL)
             return PARTCLAUSE_UNSUPPORTED;    /* step generation failed */
@@ -2985,89 +3106,38 @@ pull_exec_paramids_walker(Node *node, Bitmapset **context)
 }

 /*
- * analyze_partkey_exprs
- *        Loop through all pruning steps and identify which ones require
- *        executor startup-time or executor run-time pruning.
- *
- * Returns true if any executor partition pruning should be attempted at this
- * level.  Also fills fields of *pinfo to record how to process each step.
+ * get_partkey_exec_paramids
+ *        Loop through given pruning steps and find out which exec Params
+ *        are used.
  *
- * Note: when this is called, not much of *pinfo is valid; but that's OK
- * since we only use it as an output area.
+ * Returns a Bitmapset of Param IDs.
  */
-static bool
-analyze_partkey_exprs(PartitionedRelPruneInfo *pinfo, List *steps,
-                      int partnatts)
+static Bitmapset *
+get_partkey_exec_paramids(List *steps)
 {
-    bool        doruntimeprune = false;
+    Bitmapset  *execparamids = NULL;
     ListCell   *lc;

-    /*
-     * Steps require run-time pruning if they contain EXEC_PARAM Params.
-     * Otherwise, if their expressions aren't simple Consts or they involve
-     * non-immutable comparison operators, they require startup-time pruning.
-     * (Otherwise, the pruning would have been done at plan time.)
-     *
-     * Notice that what we actually check for mutability is the comparison
-     * functions, not the original operators.  This relies on the support
-     * functions of the btree or hash opfamily being marked consistently with
-     * the operators.
-     */
-    pinfo->nexprs = list_length(steps) * partnatts;
-    pinfo->hasexecparam = (bool *) palloc0(sizeof(bool) * pinfo->nexprs);
-    pinfo->do_initial_prune = false;
-    pinfo->do_exec_prune = false;
-    pinfo->execparamids = NULL;
-
     foreach(lc, steps)
     {
         PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc);
         ListCell   *lc2;
-        ListCell   *lc3;
-        int            keyno;

         if (!IsA(step, PartitionPruneStepOp))
             continue;

-        keyno = 0;
-        Assert(list_length(step->exprs) == list_length(step->cmpfns));
-        forboth(lc2, step->exprs, lc3, step->cmpfns)
+        foreach(lc2, step->exprs)
         {
             Expr       *expr = lfirst(lc2);
-            Oid            fnoid = lfirst_oid(lc3);

+            /* We can be quick for plain Consts */
             if (!IsA(expr, Const))
-            {
-                Bitmapset  *execparamids = pull_exec_paramids(expr);
-                bool        hasexecparams;
-                int            stateidx = PruneCxtStateIdx(partnatts,
-                                                        step->step.step_id,
-                                                        keyno);
-
-                Assert(stateidx < pinfo->nexprs);
-                hasexecparams = !bms_is_empty(execparamids);
-                pinfo->hasexecparam[stateidx] = hasexecparams;
-                pinfo->execparamids = bms_join(pinfo->execparamids,
-                                               execparamids);
-
-                if (hasexecparams)
-                    pinfo->do_exec_prune = true;
-                else
-                    pinfo->do_initial_prune = true;
-
-                doruntimeprune = true;
-            }
-            else if (func_volatile(fnoid) != PROVOLATILE_IMMUTABLE)
-            {
-                /* No exec params here, but must do initial pruning */
-                pinfo->do_initial_prune = true;
-                doruntimeprune = true;
-            }
-            keyno++;
+                execparamids = bms_join(execparamids,
+                                        pull_exec_paramids(expr));
         }
     }

-    return doruntimeprune;
+    return execparamids;
 }

 /*
@@ -3125,56 +3195,54 @@ perform_pruning_base_step(PartitionPruneContext *context,
             Expr       *expr;
             Datum        datum;
             bool        isnull;
+            Oid            cmpfn;

             expr = lfirst(lc1);
             stateidx = PruneCxtStateIdx(context->partnatts,
                                         opstep->step.step_id, keyno);
-            if (partkey_datum_from_expr(context, expr, stateidx,
-                                        &datum, &isnull))
-            {
-                Oid            cmpfn;
+            partkey_datum_from_expr(context, expr, stateidx,
+                                    &datum, &isnull);

-                /*
-                 * Since we only allow strict operators in pruning steps, any
-                 * null-valued comparison value must cause the comparison to
-                 * fail, so that no partitions could match.
-                 */
-                if (isnull)
-                {
-                    PruneStepResult *result;
-
-                    result = (PruneStepResult *) palloc(sizeof(PruneStepResult));
-                    result->bound_offsets = NULL;
-                    result->scan_default = false;
-                    result->scan_null = false;
+            /*
+             * Since we only allow strict operators in pruning steps, any
+             * null-valued comparison value must cause the comparison to fail,
+             * so that no partitions could match.
+             */
+            if (isnull)
+            {
+                PruneStepResult *result;

-                    return result;
-                }
+                result = (PruneStepResult *) palloc(sizeof(PruneStepResult));
+                result->bound_offsets = NULL;
+                result->scan_default = false;
+                result->scan_null = false;

-                /* Set up the stepcmpfuncs entry, unless we already did */
-                cmpfn = lfirst_oid(lc2);
-                Assert(OidIsValid(cmpfn));
-                if (cmpfn != context->stepcmpfuncs[stateidx].fn_oid)
-                {
-                    /*
-                     * If the needed support function is the same one cached
-                     * in the relation's partition key, copy the cached
-                     * FmgrInfo.  Otherwise (i.e., when we have a cross-type
-                     * comparison), an actual lookup is required.
-                     */
-                    if (cmpfn == context->partsupfunc[keyno].fn_oid)
-                        fmgr_info_copy(&context->stepcmpfuncs[stateidx],
-                                       &context->partsupfunc[keyno],
-                                       context->ppccontext);
-                    else
-                        fmgr_info_cxt(cmpfn, &context->stepcmpfuncs[stateidx],
-                                      context->ppccontext);
-                }
+                return result;
+            }

-                values[keyno] = datum;
-                nvalues++;
+            /* Set up the stepcmpfuncs entry, unless we already did */
+            cmpfn = lfirst_oid(lc2);
+            Assert(OidIsValid(cmpfn));
+            if (cmpfn != context->stepcmpfuncs[stateidx].fn_oid)
+            {
+                /*
+                 * If the needed support function is the same one cached in
+                 * the relation's partition key, copy the cached FmgrInfo.
+                 * Otherwise (i.e., when we have a cross-type comparison), an
+                 * actual lookup is required.
+                 */
+                if (cmpfn == context->partsupfunc[keyno].fn_oid)
+                    fmgr_info_copy(&context->stepcmpfuncs[stateidx],
+                                   &context->partsupfunc[keyno],
+                                   context->ppccontext);
+                else
+                    fmgr_info_cxt(cmpfn, &context->stepcmpfuncs[stateidx],
+                                  context->ppccontext);
             }

+            values[keyno] = datum;
+            nvalues++;
+
             lc1 = lnext(lc1);
             lc2 = lnext(lc2);
         }
@@ -3392,16 +3460,17 @@ match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey,
  * partkey_datum_from_expr
  *        Evaluate expression for potential partition pruning
  *
- * Evaluate 'expr', whose ExprState is stateidx of the context exprstate
- * array; set *value and *isnull to the resulting Datum and nullflag.
- * Return true if evaluation was possible, otherwise false.
+ * Evaluate 'expr'; set *value and *isnull to the resulting Datum and nullflag.
+ *
+ * If expr isn't a Const, its ExprState is in stateidx of the context
+ * exprstate array.
  *
  * Note that the evaluated result may be in the per-tuple memory context of
  * context->planstate->ps_ExprContext, and we may have leaked other memory
  * there too.  This memory must be recovered by resetting that ExprContext
  * after we're done with the pruning operation (see execPartition.c).
  */
-static bool
+static void
 partkey_datum_from_expr(PartitionPruneContext *context,
                         Expr *expr, int stateidx,
                         Datum *value, bool *isnull)
@@ -3413,35 +3482,20 @@ partkey_datum_from_expr(PartitionPruneContext *context,

         *value = con->constvalue;
         *isnull = con->constisnull;
-        return true;
     }
     else
     {
+        ExprState  *exprstate;
+        ExprContext *ectx;
+
         /*
          * We should never see a non-Const in a step unless we're running in
          * the executor.
          */
         Assert(context->planstate != NULL);

-        /*
-         * When called from the executor we'll have a valid planstate so we
-         * may be able to evaluate an expression which could not be folded to
-         * a Const during planning.  Since run-time pruning can occur both
-         * during initialization of the executor or while it's running, we
-         * must be careful here to evaluate expressions containing PARAM_EXEC
-         * Params only when told it's OK.
-         */
-        if (context->evalexecparams || !context->exprhasexecparam[stateidx])
-        {
-            ExprState  *exprstate;
-            ExprContext *ectx;
-
-            exprstate = context->exprstates[stateidx];
-            ectx = context->planstate->ps_ExprContext;
-            *value = ExecEvalExprSwitchContext(exprstate, ectx, isnull);
-            return true;
-        }
+        exprstate = context->exprstates[stateidx];
+        ectx = context->planstate->ps_ExprContext;
+        *value = ExecEvalExprSwitchContext(exprstate, ectx, isnull);
     }
-
-    return false;
 }
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index b363aba..465a975 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -62,24 +62,24 @@ typedef struct PartitionRoutingInfo
  * subpart_map                    Subpart index by partition index, or -1.
  * present_parts                A Bitmapset of the partition indexes that we
  *                                have subplans or subparts for.
- * context                        Contains the context details required to call
- *                                the partition pruning code.
- * pruning_steps                List of PartitionPruneSteps used to
- *                                perform the actual pruning.
- * do_initial_prune                true if pruning should be performed during
- *                                executor startup (for this partitioning level).
- * do_exec_prune                true if pruning should be performed during
- *                                executor run (for this partitioning level).
+ * initial_pruning_steps        List of PartitionPruneSteps used to
+ *                                perform executor startup pruning.
+ * exec_pruning_steps            List of PartitionPruneSteps used to
+ *                                perform per-scan pruning.
+ * initial_context                If initial_pruning_steps isn't NIL, contains
+ *                                the details needed to execute those steps.
+ * exec_context                    If exec_pruning_steps isn't NIL, contains
+ *                                the details needed to execute those steps.
  */
 typedef struct PartitionedRelPruningData
 {
     int           *subplan_map;
     int           *subpart_map;
     Bitmapset  *present_parts;
-    PartitionPruneContext context;
-    List       *pruning_steps;
-    bool        do_initial_prune;
-    bool        do_exec_prune;
+    List       *initial_pruning_steps;
+    List       *exec_pruning_steps;
+    PartitionPruneContext initial_context;
+    PartitionPruneContext exec_context;
 } PartitionedRelPruningData;

 /*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 1cce762..1241245 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -1109,21 +1109,23 @@ typedef struct PartitionedRelPruneInfo
 {
     NodeTag        type;
     Index        rtindex;        /* RT index of partition rel for this level */
-    List       *pruning_steps;    /* List of PartitionPruneStep, see below */
     Bitmapset  *present_parts;    /* Indexes of all partitions which subplans or
-                                 * subparts are present for. */
-    int            nparts;            /* Length of subplan_map[] and subpart_map[] */
-    int            nexprs;            /* Length of hasexecparam[] */
+                                 * subparts are present for */
+    int            nparts;            /* Length of the following arrays: */
     int           *subplan_map;    /* subplan index by partition index, or -1 */
     int           *subpart_map;    /* subpart index by partition index, or -1 */
     Oid           *relid_map;        /* relation OID by partition index, or 0 */
-    bool       *hasexecparam;    /* true if corresponding pruning_step contains
-                                 * any PARAM_EXEC Params. */
-    bool        do_initial_prune;    /* true if pruning should be performed
-                                     * during executor startup. */
-    bool        do_exec_prune;    /* true if pruning should be performed during
-                                 * executor run. */
-    Bitmapset  *execparamids;    /* All PARAM_EXEC Param IDs in pruning_steps */
+
+    /*
+     * initial_pruning_steps shows how to prune during executor startup (i.e.,
+     * without use of any PARAM_EXEC Params); it is NIL if no startup pruning
+     * is required.  exec_pruning_steps shows how to prune with PARAM_EXEC
+     * Params; it is NIL if no per-scan pruning is required.
+     */
+    List       *initial_pruning_steps;    /* List of PartitionPruneStep */
+    List       *exec_pruning_steps; /* List of PartitionPruneStep */
+    Bitmapset  *execparamids;    /* All PARAM_EXEC Param IDs in
+                                 * exec_pruning_steps */
 } PartitionedRelPruneInfo;

 /*
diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h
index 2f75717..b906ae1 100644
--- a/src/include/partitioning/partprune.h
+++ b/src/include/partitioning/partprune.h
@@ -44,10 +44,6 @@ struct RelOptInfo;
  * exprstates        Array of ExprStates, indexed as per PruneCtxStateIdx; one
  *                    for each partition key in each pruning step.  Allocated if
  *                    planstate is non-NULL, otherwise NULL.
- * exprhasexecparam    Array of bools, each true if corresponding 'exprstate'
- *                    expression contains any PARAM_EXEC Params.  (Can be NULL
- *                    if planstate is NULL.)
- * evalexecparams    True if it's safe to evaluate PARAM_EXEC Params.
  */
 typedef struct PartitionPruneContext
 {
@@ -61,8 +57,6 @@ typedef struct PartitionPruneContext
     MemoryContext ppccontext;
     PlanState  *planstate;
     ExprState **exprstates;
-    bool       *exprhasexecparam;
-    bool        evalexecparams;
 } PartitionPruneContext;

 /*
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 723fc70..841bd8b 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -3149,6 +3149,45 @@ select * from mc3p where a < 3 and abs(b) = 1;
          Filter: ((a < 3) AND (abs(b) = 1))
 (7 rows)

+--
+-- 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
+--
+set plan_cache_mode = force_generic_plan;
+prepare ps1 as
+  select * from mc3p where a = $1 and abs(b) < (select 3);
+explain (analyze, costs off, summary off, timing off)
+execute ps1(1);
+                   QUERY PLAN
+-------------------------------------------------
+ Append (actual rows=1 loops=1)
+   InitPlan 1 (returns $0)
+     ->  Result (actual rows=1 loops=1)
+   Subplans Removed: 2
+   ->  Seq Scan on mc3p1 (actual rows=1 loops=1)
+         Filter: ((a = $1) AND (abs(b) < $0))
+(6 rows)
+
+deallocate ps1;
+prepare ps2 as
+  select * from mc3p where a <= $1 and abs(b) < (select 3);
+explain (analyze, costs off, summary off, timing off)
+execute ps2(1);
+                   QUERY PLAN
+-------------------------------------------------
+ Append (actual rows=2 loops=1)
+   InitPlan 1 (returns $0)
+     ->  Result (actual rows=1 loops=1)
+   Subplans Removed: 1
+   ->  Seq Scan on mc3p0 (actual rows=1 loops=1)
+         Filter: ((a <= $1) AND (abs(b) < $0))
+   ->  Seq Scan on mc3p1 (actual rows=1 loops=1)
+         Filter: ((a <= $1) AND (abs(b) < $0))
+(8 rows)
+
+deallocate ps2;
+reset plan_cache_mode;
 drop table mc3p;
 -- Ensure runtime pruning works with initplans params with boolean types
 create table boolvalues (value bool not null);
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index 2373bd8..071e28d 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -809,6 +809,24 @@ insert into mc3p values (0, 1, 1), (1, 1, 1), (2, 1, 1);
 explain (analyze, costs off, summary off, timing off)
 select * from mc3p where a < 3 and abs(b) = 1;

+--
+-- 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
+--
+set plan_cache_mode = force_generic_plan;
+prepare ps1 as
+  select * from mc3p where a = $1 and abs(b) < (select 3);
+explain (analyze, costs off, summary off, timing off)
+execute ps1(1);
+deallocate ps1;
+prepare ps2 as
+  select * from mc3p where a <= $1 and abs(b) < (select 3);
+explain (analyze, costs off, summary off, timing off)
+execute ps2(1);
+deallocate ps2;
+reset plan_cache_mode;
+
 drop table mc3p;

 -- Ensure runtime pruning works with initplans params with boolean types
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a6c6de7..a06f28a 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void show_sort_keys(SortState *so
*** 82,87 ****
--- 82,89 ----
                 ExplainState *es);
  static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
                         ExplainState *es);
+ static void show_pruning_info(PartitionPruneInfo *pinfo, List *ancestors,
+                        ExplainState *es);
  static void show_agg_keys(AggState *astate, List *ancestors,
                ExplainState *es);
  static void show_grouping_sets(PlanState *planstate, Agg *agg,
*************** ExplainNode(PlanState *planstate, List *
*** 1841,1849 ****
--- 1843,1857 ----
              show_sort_keys(castNode(SortState, planstate), ancestors, es);
              show_sort_info(castNode(SortState, planstate), es);
              break;
+         case T_Append:
+             show_pruning_info(((Append *) plan)->part_prune_info,
+                               ancestors, es);
+             break;
          case T_MergeAppend:
              show_merge_append_keys(castNode(MergeAppendState, planstate),
                                     ancestors, es);
+             show_pruning_info(((MergeAppend *) plan)->part_prune_info,
+                               ancestors, es);
              break;
          case T_Result:
              show_upper_qual((List *) ((Result *) plan)->resconstantqual,
*************** show_merge_append_keys(MergeAppendState
*** 2198,2203 ****
--- 2206,2258 ----
  }

  /*
+  * Show runtime pruning info, if any
+  */
+ static void
+ show_pruning_info(PartitionPruneInfo *ppinfo, List *ancestors,
+                   ExplainState *es)
+ {
+     ListCell   *lc;
+
+     if (ppinfo == NULL)
+         return;
+     Assert(IsA(ppinfo, PartitionPruneInfo));
+     foreach(lc, ppinfo->prune_infos)
+     {
+         List       *prune_infos = lfirst(lc);
+         ListCell   *l2;
+
+         foreach(l2, prune_infos)
+         {
+             PartitionedRelPruneInfo *pinfo = lfirst(l2);
+
+             if (pinfo->initial_pruning_steps ||
+                 pinfo->exec_pruning_steps)
+             {
+                 Index rti = pinfo->rtindex;
+                 RangeTblEntry *rte;
+                 char       *refname;
+
+                 rte = rt_fetch(rti, es->rtable);
+                 refname = (char *) list_nth(es->rtable_names, rti - 1);
+                 if (refname == NULL)
+                     refname = rte->eref->aliasname;
+
+                 if (es->format == EXPLAIN_FORMAT_TEXT)
+                 {
+                     appendStringInfoSpaces(es->str, es->indent * 2);
+                     appendStringInfo(es->str,
+                                      "Runtime Pruning: %s has %d initial steps, %d exec steps\n",
+                                      refname,
+                                      list_length(pinfo->initial_pruning_steps),
+                                      list_length(pinfo->exec_pruning_steps));
+                 }
+             }
+         }
+     }
+ }
+
+ /*
   * Show the grouping keys for an Agg node.
   */
  static void
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a6c6de7..19708ab 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void show_sort_keys(SortState *so
*** 82,87 ****
--- 82,89 ----
                 ExplainState *es);
  static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
                         ExplainState *es);
+ static void show_pruning_info(PartitionPruneInfo *pinfo, List *ancestors,
+                        ExplainState *es);
  static void show_agg_keys(AggState *astate, List *ancestors,
                ExplainState *es);
  static void show_grouping_sets(PlanState *planstate, Agg *agg,
*************** ExplainNode(PlanState *planstate, List *
*** 1841,1849 ****
--- 1843,1857 ----
              show_sort_keys(castNode(SortState, planstate), ancestors, es);
              show_sort_info(castNode(SortState, planstate), es);
              break;
+         case T_Append:
+             show_pruning_info(((Append *) plan)->part_prune_info,
+                               ancestors, es);
+             break;
          case T_MergeAppend:
              show_merge_append_keys(castNode(MergeAppendState, planstate),
                                     ancestors, es);
+             show_pruning_info(((MergeAppend *) plan)->part_prune_info,
+                               ancestors, es);
              break;
          case T_Result:
              show_upper_qual((List *) ((Result *) plan)->resconstantqual,
*************** show_merge_append_keys(MergeAppendState
*** 2198,2203 ****
--- 2206,2258 ----
  }

  /*
+  * Show runtime pruning info, if any
+  */
+ static void
+ show_pruning_info(PartitionPruneInfo *ppinfo, List *ancestors,
+                   ExplainState *es)
+ {
+     ListCell   *lc;
+
+     if (ppinfo == NULL)
+         return;
+     Assert(IsA(ppinfo, PartitionPruneInfo));
+     foreach(lc, ppinfo->prune_infos)
+     {
+         List       *prune_infos = lfirst(lc);
+         ListCell   *l2;
+
+         foreach(l2, prune_infos)
+         {
+             PartitionedRelPruneInfo *pinfo = lfirst(l2);
+
+             if (pinfo->do_initial_prune ||
+                 pinfo->do_exec_prune)
+             {
+                 Index rti = pinfo->rtindex;
+                 RangeTblEntry *rte;
+                 char       *refname;
+
+                 rte = rt_fetch(rti, es->rtable);
+                 refname = (char *) list_nth(es->rtable_names, rti - 1);
+                 if (refname == NULL)
+                     refname = rte->eref->aliasname;
+
+                 if (es->format == EXPLAIN_FORMAT_TEXT)
+                 {
+                     appendStringInfoSpaces(es->str, es->indent * 2);
+                     appendStringInfo(es->str,
+                                      "Runtime Pruning: %s has %d initial steps, %d exec steps\n",
+                                      refname,
+                                      pinfo->do_initial_prune ? list_length(pinfo->pruning_steps) : 0,
+                                      pinfo->do_exec_prune ? list_length(pinfo->pruning_steps) : 0);
+                 }
+             }
+         }
+     }
+ }
+
+ /*
   * Show the grouping keys for an Agg node.
   */
  static void

pgsql-bugs by date:

Previous
From: Andres Freund
Date:
Subject: Re: BUG #15804: Assertion failure when using logging_collector withEXEC_BACKEND
Next
From: Andrew Gierth
Date:
Subject: Re: BUG #15812: Select statement of a very big number, with a division operator seems to round up.