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..a892ca1 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -85,6 +85,17 @@ typedef enum PartClauseMatchStatus
 } PartClauseMatchStatus;
 
 /*
+ * PartClauseTarget
+ *		Identifies which qual clauses we can use for generating pruning steps
+ */
+typedef enum PartClauseTarget
+{
+	PARTCLAUSE_PLANNER,			/* want to prune during planning */
+	PARTCLAUSE_INITIAL,			/* want to prune during executor startup */
+	PARTCLAUSE_EXEC				/* want to prune for each plan node scan */
+} PartClauseTarget;
+
+/*
  * GeneratePruningStepsContext
  *		Information about the current state of generation of "pruning steps"
  *		for a given set of clauses
@@ -95,8 +106,7 @@ typedef enum PartClauseMatchStatus
 typedef struct GeneratePruningStepsContext
 {
 	/* Input data: */
-	bool		forplanner;		/* true when generating steps to be used
-								 * during query planning */
+	PartClauseTarget target;	/* context we're generating steps for */
 	/* Working state and result data: */
 	int			next_step_id;
 	List	   *steps;			/* output, list of PartitionPruneSteps */
@@ -122,7 +132,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 forplanner, bool *contradictory);
+					PartClauseTarget target, bool *contradictory);
 static List *gen_partprune_steps_internal(GeneratePruningStepsContext *context,
 							 RelOptInfo *rel, List *clauses,
 							 bool *contradictory);
@@ -169,8 +179,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,
@@ -347,9 +356,9 @@ 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;
+		List	   *initial_pruning_steps;
+		List	   *exec_pruning_steps;
 		bool		contradictory;
 
 		/*
@@ -415,13 +424,13 @@ 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 need to do this twice,
+		 * once to obtain executor startup pruning steps, and once for
+		 * executor per-scan pruning steps.
 		 */
-		pruning_steps = gen_partprune_steps(subpart, partprunequal, false,
-											&contradictory);
+		initial_pruning_steps = gen_partprune_steps(subpart, partprunequal,
+													PARTCLAUSE_INITIAL,
+													&contradictory);
 
 		if (contradictory)
 		{
@@ -437,20 +446,28 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 			return NIL;
 		}
 
+		exec_pruning_steps = gen_partprune_steps(subpart, partprunequal,
+												 PARTCLAUSE_EXEC,
+												 &contradictory);
+
+		if (contradictory)
+		{
+			/* As above, abort run-time pruning if anything fishy happens */
+			return NIL;
+		}
+
+		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 = get_partkey_exec_paramids(exec_pruning_steps);
 		/* 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)
@@ -535,6 +552,8 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
  *		Process 'clauses' (a rel's baserestrictinfo list of clauses) and return
  *		a list of "partition pruning steps".
  *
+ * XXX: FIX COMMENTS
+ *
  * 'forplanner' must be true when generating steps to be evaluated during
  * query planning, false when generating steps to be used at run-time.
  *
@@ -555,12 +574,12 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
  * else it's set false.
  */
 static List *
-gen_partprune_steps(RelOptInfo *rel, List *clauses, bool forplanner,
+gen_partprune_steps(RelOptInfo *rel, List *clauses, PartClauseTarget target,
 					bool *contradictory)
 {
 	GeneratePruningStepsContext context;
 
-	context.forplanner = forplanner;
+	context.target = target;
 	context.next_step_id = 0;
 	context.steps = NIL;
 
@@ -635,7 +654,8 @@ prune_append_rel_partitions(RelOptInfo *rel)
 	 * pruning code that we only want pruning steps that can be evaluated
 	 * during planning.
 	 */
-	pruning_steps = gen_partprune_steps(rel, clauses, true,
+	pruning_steps = gen_partprune_steps(rel, clauses,
+										PARTCLAUSE_PLANNER,
 										&contradictory);
 	if (contradictory)
 		return NULL;
@@ -655,8 +675,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);
@@ -1656,7 +1674,7 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		 * will produce a usable result, since that'd mean there are Vars on
 		 * both sides.)
 		 */
-		if (context->forplanner)
+		if (context->target == PARTCLAUSE_PLANNER)
 		{
 			/*
 			 * When pruning in the planner, we only support pruning using
@@ -1675,6 +1693,8 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		}
 		else
 		{
+			Bitmapset  *paramids;
+
 			/*
 			 * Otherwise, non-consts are allowed, but we can't prune using an
 			 * expression that contains Vars.
@@ -1683,6 +1703,14 @@ match_clause_to_partition_key(RelOptInfo *rel,
 				return PARTCLAUSE_UNSUPPORTED;
 
 			/*
+			 * See if there are exec Params, too.  If so, we can only use this
+			 * expression during per-scan pruning.
+			 */
+			paramids = pull_exec_paramids(expr);
+			if (!bms_is_empty(paramids) && context->target != PARTCLAUSE_EXEC)
+				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
@@ -1837,7 +1865,7 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		 * see if it can sanely be used for partition pruning (this is
 		 * identical to the logic for a plain OpExpr).
 		 */
-		if (context->forplanner)
+		if (context->target == PARTCLAUSE_PLANNER)
 		{
 			/*
 			 * When pruning in the planner, we only support pruning using
@@ -1856,6 +1884,8 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		}
 		else
 		{
+			Bitmapset  *paramids;
+
 			/*
 			 * Otherwise, non-consts are allowed, but we can't prune using an
 			 * expression that contains Vars.
@@ -1864,6 +1894,14 @@ match_clause_to_partition_key(RelOptInfo *rel,
 				return PARTCLAUSE_UNSUPPORTED;
 
 			/*
+			 * See if there are exec Params, too.  If so, we can only use this
+			 * expression during per-scan pruning.
+			 */
+			paramids = pull_exec_paramids(expr);
+			if (!bms_is_empty(paramids) && context->target != PARTCLAUSE_EXEC)
+				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
@@ -2985,89 +3023,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;
 }
 
 /*
@@ -3392,6 +3379,8 @@ match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey,
  * partkey_datum_from_expr
  *		Evaluate expression for potential partition pruning
  *
+ * XXX this can't fail anymore, so change to return void
+ *
  * 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.
@@ -3417,30 +3406,19 @@ partkey_datum_from_expr(PartitionPruneContext *context,
 	}
 	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 true;
 	}
 
 	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
