diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c83991c93c..f185e47889 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -48,9 +48,9 @@ static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 bool *isnull,
 									 int maxfieldlen);
 static List *adjust_partition_tlist(List *tlist, TupleConversionMap *map);
-static void find_subplans_for_params_recurse(PartitionPruneState *prunestate,
+static int find_subplans_for_params_recurse(PartitionPruneState *prunestate,
 								 PartitionPruningData *pprune,
-								 bool allparams,
+								 bool initial_prune,
 								 Bitmapset **validsubplans);
 
 
@@ -1334,54 +1334,53 @@ adjust_partition_tlist(List *tlist, TupleConversionMap *map)
  * Run-Time Partition Pruning Support.
  *
  * The following series of functions exist to support the removal of unneeded
- * subnodes for queries against partitioned tables.  The supporting functions
+ * subplans for queries against partitioned tables.  The supporting functions
  * here are designed to work with any node type which supports an arbitrary
- * number of subnodes, e.g. Append, MergeAppend.
+ * number of subplans, e.g. Append, MergeAppend.
  *
- * Normally this pruning work is performed by the query planner's partition
- * pruning code, however, the planner is limited to only being able to prune
- * away unneeded partitions using quals which compare the partition key to a
- * value which is known to be Const during planning.  To allow the same
- * pruning to be performed for values which are only determined during
- * execution, we must make an additional pruning attempt during execution.
+ * Run-time pruning works in addition to plan-time pruning, however, during
+ * query planning, pruning is limited to only being able to use expressions
+ * which are known Consts.  To allow pruning to be performed using values which
+ * aren't known during planning, we must make an additional pruning attempt
+ * during execution.
  *
- * Here we support pruning using both external and exec Params.  The main
- * difference between these that we need to concern ourselves with is the
- * time when the values of the Params are known.  External Param values are
- * known at any time of execution, including executor startup, but exec Param
- * values are only known when the executor is running.
+ * Here we support pruning using any non-volatile expression, thus allowing
+ * pruning to be performed using both external and exec params, in fact, any
+ * stable expression approved by gen_partprune_steps can be evaluated and
+ * used here.  However, if an expression contains an exec param it cannot be
+ * evaluated until during execution. For everything else, we can perform
+ * pruning during executor startup.
  *
- * For external Params we may be able to prune away unneeded partitions
- * during executor startup.  This has the added benefit of not having to
- * initialize the unneeded subnodes at all.  This is useful as it can save
- * quite a bit of effort during executor startup.
+ * When performing pruning using expressions containing exec params, we must
+ * perform partition pruning again each time one of these params changes.  It
+ * is the calling code's responsibility to ensure that this happens.
  *
- * For exec Params, we must delay pruning until the executor is running.
+ * Having the ability to prune away unneeded subplans during executor startup
+ * has the added benefit of not having to initialize the unneeded subplans.
  *
  * Functions:
  *
  * ExecSetupPartitionPruneState:
- *		This must be called by nodes before any partition pruning is
- *		attempted.  Normally executor startup is a good time. This function
- *		creates the PartitionPruneState details which are required by each
- *		of the two pruning functions, details include information about
- *		how to map the partition index details which are returned by the
- *		planner's partition prune function into subnode indexes.
+ *		Creates the PartitionPruneState as required by each of the two pruning
+ *		functions.  Details stored include how to map the partition index
+ *		returned by the partition pruning code into subplans indexes.
  *
  * ExecFindInitialMatchingSubPlans:
- *		Returns indexes of matching subnodes utilizing only external Params
- *		to eliminate subnodes.  The function must only be called during
- *		executor startup for the given node before the subnodes themselves
- *		are initialized.  Subnodes which are found not to match by this
- *		function must not be included in the node's list of subnodes as this
- *		function performs a remap of the partition index to subplan index map
- *		and the newly created map provides indexes only for subnodes which
- *		remain after calling this function.
+ *		Returns indexes of matching subplans.  Here partition pruning is
+ *		performed using all expressions found in the partition pruning steps
+ *		apart from expressions containing exec params.  This function must
+ *		only be called once and must only ne called during initialization of
+ *		the node which it applies to.  Subplans which are found not to match
+ *		by this function must not be included in the node's list of subplans
+ *		as this function performs a remap of the partition index to subplan
+ *		index map and the newly created map provides indexes only for subplans
+ *		which remain after calling this function.
  *
  * ExecFindMatchingSubPlans:
- *		Returns indexes of matching subnodes utilizing all Params to eliminate
- *		subnodes which can't possibly contain matching tuples.  This function
- *		can only be called while the executor is running.
+ *		Returns indexes of matching subplans evaluating all expressions in the
+ *		pruning steps, including exec params.  This function can only be
+ *		called during execution and must be called again each time the value
+ *		of a param listed in PartitionPruneState's 'execparams' changes.
  *-------------------------------------------------------------------------
  */
 
@@ -1392,32 +1391,29 @@ adjust_partition_tlist(List *tlist, TupleConversionMap *map)
  *
  * 'partitionpruneinfo' is a List of PartitionPruneInfos as generated by
  * make_partition_pruneinfo.  Here we build a PartitionPruneContext for each
- * item in the List.  These contexts can be re-used each time we re-evaulate
+ * item in that List.  These contexts can be re-used each time we reevaluate
  * which partitions match the pruning steps provided in each
  * PartitionPruneInfo.
  */
 PartitionPruneState *
 ExecSetupPartitionPruneState(PlanState *planstate, List *partitionpruneinfo)
 {
-	PartitionPruningData *prunedata;
 	PartitionPruneState *prunestate;
 	ListCell   *lc;
 	int			i;
+	int			num_partprunedata;
+	Size		size;
 
 	Assert(partitionpruneinfo != NIL);
 
-	prunestate = (PartitionPruneState *) palloc(sizeof(PartitionPruneState));
-	prunedata = (PartitionPruningData *)
-		palloc(sizeof(PartitionPruningData) * list_length(partitionpruneinfo));
+	num_partprunedata = list_length(partitionpruneinfo);
+	size = offsetof(PartitionPruneState, partprunedata);
+	size += sizeof(PartitionPruningData) * num_partprunedata;
 
-	/*
-	 * The first item in the array contains the details for the query's target
-	 * partition, so record that as the root of the partition hierarchy.
-	 */
-	prunestate->partprunedata = prunedata;
-	prunestate->num_partprunedata = list_length(partitionpruneinfo);
-	prunestate->extparams = NULL;
+	prunestate = (PartitionPruneState *) palloc(size);
+	prunestate->num_partprunedata = num_partprunedata;
 	prunestate->execparams = NULL;
+	prunestate->do_initial_prune = false;	/* may be set below */
 
 	/*
 	 * Create a sub memory context which we'll use when making calls to the
@@ -1435,7 +1431,7 @@ ExecSetupPartitionPruneState(PlanState *planstate, List *partitionpruneinfo)
 	foreach(lc, partitionpruneinfo)
 	{
 		PartitionPruneInfo *pinfo = (PartitionPruneInfo *) lfirst(lc);
-		PartitionPruningData *pprune = &prunedata[i];
+		PartitionPruningData *pprune = &prunestate->partprunedata[i];
 		PartitionPruneContext *context = &pprune->context;
 		PartitionDesc partdesc;
 		Relation	rel;
@@ -1444,24 +1440,24 @@ ExecSetupPartitionPruneState(PlanState *planstate, List *partitionpruneinfo)
 		int			partnatts;
 		int			n_steps;
 
-		pprune->present_parts = bms_copy(pinfo->present_parts);
-		pprune->subnode_map = palloc(sizeof(int) * pinfo->nparts);
+		pprune->present_parts = pinfo->present_parts;
+		pprune->subplan_map = palloc(sizeof(int) * pinfo->nparts);
 
 		/*
 		 * We must make a copy of this rather than pointing directly to the
 		 * plan's version as we may end up making modifications to it later.
 		 */
-		memcpy(pprune->subnode_map, pinfo->subnode_map,
+		memcpy(pprune->subplan_map, pinfo->subplan_map,
 			   sizeof(int) * pinfo->nparts);
 
-		/* We can use the subpart_map verbatim, since we never modify it */
+		/* We can use the subpart_map verbatim since we never modify it */
 		pprune->subpart_map = pinfo->subpart_map;
 
 		/*
 		 * Grab some info from the table's relcache; lock was already obtained
 		 * by ExecLockNonLeafAppendTables.
 		 */
-		rel = relation_open(pinfo->reloid, NoLock);
+		rel = heap_open(pinfo->reloid, NoLock);
 
 		partkey = RelationGetPartitionKey(rel);
 		partdesc = RelationGetPartitionDesc(rel);
@@ -1476,9 +1472,14 @@ ExecSetupPartitionPruneState(PlanState *planstate, List *partitionpruneinfo)
 		context->nparts = pinfo->nparts;
 		context->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey);
 		context->planstate = planstate;
-		context->safeparams = NULL; /* empty for now */
 		context->exprstates = palloc0(sizeof(ExprState *) * n_steps * partnatts);
 
+		/*
+		 * Use the hasexecparam. This is not modified anywhere, so we just
+		 * borrow the plan's copy.
+		 */
+		context->exprhasexecparam = pinfo->hasexecparam;
+
 		/* Initialize expression states for each expression */
 		foreach(lc2, pinfo->pruning_steps)
 		{
@@ -1511,65 +1512,51 @@ ExecSetupPartitionPruneState(PlanState *planstate, List *partitionpruneinfo)
 		}
 
 		pprune->pruning_steps = pinfo->pruning_steps;
-		pprune->extparams = bms_copy(pinfo->extparams);
-		pprune->allparams = bms_union(pinfo->extparams, pinfo->execparams);
+		pprune->execparams = pinfo->execparams;
+		pprune->do_initial_prune = pinfo->do_initial_prune;
+		pprune->do_exec_prune = pinfo->do_exec_prune;
 
 		/*
-		 * Accumulate the paramids which match the partitioned keys of all
-		 * partitioned tables.
+		 * Accumulate the exec paramids which match the partitioned keys of
+		 * all partitioned tables.
 		 */
-		prunestate->extparams = bms_add_members(prunestate->extparams,
-												pinfo->extparams);
-
 		prunestate->execparams = bms_add_members(prunestate->execparams,
 												 pinfo->execparams);
 
-		relation_close(rel, NoLock);
+		/*
+		 * Record if an initial prune would be useful at any level of the
+		 * partition hierarchy.
+		 */
+		prunestate->do_initial_prune |= pinfo->do_initial_prune;
+
+		heap_close(rel, NoLock);
 
 		i++;
 	}
 
-	/*
-	 * Cache the union of the paramids of both types.  This saves having to
-	 * recalculate it everytime we need to know what they are.
-	 */
-	prunestate->allparams = bms_union(prunestate->extparams,
-									  prunestate->execparams);
-
 	return prunestate;
 }
 
 /*
  * ExecFindInitialMatchingSubPlans
- *		Determine which subset of subplan nodes we need to initialize based
- *		on the details stored in 'prunestate'.  Here we only determine the
- *		matching partitions using values known during plan startup, which is
- *		only external Params.  Exec Params will be unknown at this time.  We
- *		must delay pruning using exec Params until the actual executor run.
- *
- * It is expected that callers of this function do so only once during their
- * init plan.  The caller must only initialize the subnodes which are returned
- * by this function. The remaining subnodes should be discarded.  Once this
- * function has been called, future calls to ExecFindMatchingSubPlans will
- * return its matching subnode indexes assuming that the caller discarded
- * the original non-matching subnodes.
+ *		Determine the minimum set of subplans matching these pruning steps
+ *		without evaluation of exec Params.  We also re-map the translation
+ *		matrix which allows conversion of partition indexes into subplan index
+ *		to account for the unneeded subplans having been removed.
  *
- * This function must only be called if 'prunestate' has any extparams.
+ * Must only be called once per 'prunestate'.
  *
- * 'nsubnodes' must be passed as the total number of unpruned subnodes.
+ * 'nsubplans' must be passed as the total number of unpruned subplans.
  */
 Bitmapset *
-ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubnodes)
+ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubplans)
 {
 	PartitionPruningData *pprune;
 	MemoryContext oldcontext;
 	Bitmapset  *result = NULL;
+	int			matches;
 
-	/*
-	 * Ensure there's actually external params, or we've not been called
-	 * already.
-	 */
-	Assert(!bms_is_empty(prunestate->extparams));
+	Assert(prunestate->do_initial_prune);
 
 	pprune = prunestate->partprunedata;
 
@@ -1579,8 +1566,9 @@ ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubnodes)
 	 */
 	oldcontext = MemoryContextSwitchTo(prunestate->prune_context);
 
-	/* Determine which subnodes match the external params */
-	find_subplans_for_params_recurse(prunestate, pprune, false, &result);
+	/* Perform pruning without using exec params */
+	matches = find_subplans_for_params_recurse(prunestate, pprune, true,
+											   &result);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -1590,86 +1578,113 @@ ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubnodes)
 	MemoryContextReset(prunestate->prune_context);
 
 	/*
-	 * Record that partition pruning has been performed for external params.
-	 * These are not required again afterwards, and nullifying them helps
-	 * ensure nothing accidentally calls this function twice on the same
-	 * PartitionPruneState.
-	 *
-	 * (Note we keep prunestate->allparams, because we do use that one
-	 * repeatedly in ExecFindMatchingSubPlans).
-	 */
-	bms_free(prunestate->extparams);
-	prunestate->extparams = NULL;
-
-	/*
-	 * If any subnodes were pruned, we must re-sequence the subnode indexes so
+	 * If any subplans were pruned, we must re-sequence the subplan indexes so
 	 * that ExecFindMatchingSubPlans properly returns the indexes from the
-	 * subnodes which will remain after execution of this function.
+	 * subplans which will remain after execution of this function.
 	 */
-	if (bms_num_members(result) < nsubnodes)
+	if (matches < nsubplans)
 	{
-		int		   *new_subnode_indexes;
+		int		   *new_subplan_indexes;
 		int			i;
 		int			newidx;
+		int			num_partprunedata;
 
 		/*
-		 * First we must build an array which we can use to adjust the
-		 * existing subnode_map so that it contains the new subnode indexes.
+		 * First we must build an array which to stores the new 1-based index
+		 * of the subplan node.  Elements which are set to 0 after this are
+		 * newly pruned partitions.
 		 */
-		new_subnode_indexes = (int *) palloc(sizeof(int) * nsubnodes);
-		newidx = 0;
-		for (i = 0; i < nsubnodes; i++)
-		{
-			if (bms_is_member(i, result))
-				new_subnode_indexes[i] = newidx++;
-			else
-				new_subnode_indexes[i] = -1;	/* Newly pruned */
-		}
+		new_subplan_indexes = (int *) palloc0(sizeof(int) * nsubplans);
+		newidx = 1;
+		i = -1;
+		while ((i = bms_next_member(result, i)) >= 0)
+			new_subplan_indexes[i] = newidx++;
+
+		num_partprunedata = prunestate->num_partprunedata;
 
 		/*
-		 * Now we can re-sequence each PartitionPruneInfo's subnode_map so
-		 * that they point to the new index of the subnode.
+		 * Now we can re-sequence each PartitionPruneInfo's subplan_map so
+		 * that they point to the new index of the subplan.
 		 */
-		for (i = 0; i < prunestate->num_partprunedata; i++)
+		for (i = 0; i < num_partprunedata; i++)
 		{
-			int			nparts;
+			Bitmapset  *new_parts = NULL;
 			int			j;
 
 			pprune = &prunestate->partprunedata[i];
-			nparts = pprune->context.nparts;
 
-			/*
-			 * We also need to reset the present_parts field so that it only
-			 * contains partition indexes that we actually still have subnodes
-			 * for.  It seems easier to build a fresh one, rather than trying
-			 * to update the existing one.
-			 */
-			bms_free(pprune->present_parts);
-			pprune->present_parts = NULL;
-
-			for (j = 0; j < nparts; j++)
+			/* Redetermine which partitions are now present. */
+			j = -1;
+			while ((j = bms_next_member(pprune->present_parts, j)) >= 0)
 			{
-				int			oldidx = pprune->subnode_map[j];
+				int			oldidx = pprune->subplan_map[j];
 
 				/*
-				 * If this partition existed as a subnode then change the old
-				 * subnode index to the new subnode index.  The new index may
+				 * If this partition existed as a subplan then change the old
+				 * subplan index to the new subplan index.  The new index may
 				 * become -1 if the partition was pruned above, or it may just
-				 * come earlier in the subnode list due to some subnodes being
+				 * come earlier in the subplan list due to some subplans being
 				 * removed earlier in the list.
 				 */
 				if (oldidx >= 0)
 				{
-					pprune->subnode_map[j] = new_subnode_indexes[oldidx];
+					int			newidx = new_subplan_indexes[oldidx] - 1;
+
+					pprune->subplan_map[j] = newidx;
 
-					if (new_subnode_indexes[oldidx] >= 0)
-						pprune->present_parts =
-							bms_add_member(pprune->present_parts, j);
+					if (newidx >= 0)
+						new_parts = bms_add_member(new_parts, j);
 				}
 			}
+
+			/*
+			 * Replace without pfreeing the original, since that memory
+			 * belongs to the plan.
+			 */
+			pprune->present_parts = new_parts;
 		}
 
-		pfree(new_subnode_indexes);
+		/*
+		 * Now we must determine which sub-partitioned tables still have
+		 * unpruned partitions.  The easiest way to do this is to simply loop
+		 * over each PartitionPruningData again checking if there are any
+		 * 'present_parts' for the sub-partitioned table.  We needn't bother
+		 * doing this if there are no sub-partitioned tables.
+		 */
+		if (num_partprunedata > 1)
+		{
+			for (i = 0; i < num_partprunedata; i++)
+			{
+				int			nparts;
+				int			j;
+
+				pprune = &prunestate->partprunedata[i];
+				nparts = pprune->context.nparts;
+
+				/*
+				 * XXX if we still had the old present_parts available then we
+				 * could do a bms_next_member() loop here.  Is it worth
+				 * stashing it somewhere to allow that?
+				 */
+				for (j = 0; j < nparts; j++)
+				{
+					int			subidx = pprune->subpart_map[j];
+
+					if (subidx >= 0)
+					{
+						PartitionPruningData *subprune;
+
+						subprune = &prunestate->partprunedata[subidx];
+
+						if (!bms_is_empty(subprune->present_parts))
+							pprune->present_parts =
+								bms_add_member(pprune->present_parts, j);
+					}
+				}
+			}
+		}
+
+		pfree(new_subplan_indexes);
 	}
 
 	return result;
@@ -1677,10 +1692,8 @@ ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubnodes)
 
 /*
  * ExecFindMatchingSubPlans
- *		Determine which subplans match the pruning steps detailed in
- *		'pprune' for the current Param values.
- *
- * Here we utilize both external and exec Params for pruning.
+ *		Determine which subplan are required based on the sets of pruning
+ *		steps stored in 'prunestate'.
  */
 Bitmapset *
 ExecFindMatchingSubPlans(PartitionPruneState *prunestate)
@@ -1697,7 +1710,7 @@ ExecFindMatchingSubPlans(PartitionPruneState *prunestate)
 	 */
 	oldcontext = MemoryContextSwitchTo(prunestate->prune_context);
 
-	find_subplans_for_params_recurse(prunestate, pprune, true, &result);
+	find_subplans_for_params_recurse(prunestate, pprune, false, &result);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -1712,73 +1725,68 @@ ExecFindMatchingSubPlans(PartitionPruneState *prunestate)
 /*
  * find_subplans_for_params_recurse
  *		Recursive worker function for ExecFindMatchingSubPlans and
- *		ExecFindInitialMatchingSubPlans
+ *		ExecFindInitialMatchingSubPlans.
+ *
+ * Returns the number of matching subplans found.
  */
-static void
+static int
 find_subplans_for_params_recurse(PartitionPruneState *prunestate,
 								 PartitionPruningData *pprune,
-								 bool allparams,
+								 bool initial_prune,
 								 Bitmapset **validsubplans)
 {
 	PartitionPruneContext *context = &pprune->context;
 	Bitmapset  *partset;
-	Bitmapset  *pruneparams;
+	int			matches = 0;
+	int			partidx;
 	int			i;
 
 	/* Guard against stack overflow due to overly deep partition hierarchy. */
 	check_stack_depth();
 
 	/*
-	 * Use only external params unless we've been asked to also use exec
-	 * params too.
-	 */
-	if (allparams)
-		pruneparams = pprune->allparams;
-	else
-		pruneparams = pprune->extparams;
-
-	/*
-	 * We only need to determine the matching partitions if there are any
-	 * params matching the partition key at this level.  If there are no
-	 * matching params, then we can simply return all subnodes which belong to
-	 * this parent partition.  The planner should have already determined
-	 * these to be the minimum possible set.  We must still recursively visit
-	 * any subpartitioned tables as we may find their partition keys match
-	 * some Params at their level.
+	 * Only prune if pruning would be useful at this level.  Pruning is free
+	 * to evaluate exec params when initial_prune is false.
 	 */
-	if (!bms_is_empty(pruneparams))
+	if (!initial_prune && pprune->do_exec_prune)
 	{
-		context->safeparams = pruneparams;
+		context->evalexecparams = true;
+		partset = get_matching_partitions(context,
+										  pprune->pruning_steps);
+	}
+	else if (initial_prune && pprune->do_initial_prune)
+	{
+		context->evalexecparams = false;
 		partset = get_matching_partitions(context,
 										  pprune->pruning_steps);
 	}
 	else
+	{
+		/* No pruning required?  Just include all partitions. */
 		partset = pprune->present_parts;
+	}
 
-	/* Translate partset into subnode indexes */
+	/* Translate partset into subplan indexes */
 	i = -1;
 	while ((i = bms_next_member(partset, i)) >= 0)
 	{
-		if (pprune->subnode_map[i] >= 0)
+		if (pprune->subplan_map[i] >= 0)
+		{
 			*validsubplans = bms_add_member(*validsubplans,
-											pprune->subnode_map[i]);
+											pprune->subplan_map[i]);
+			matches++;
+		}
+
+		/* Recurse if the partition is a partitioned table */
+		else if ((partidx = pprune->subpart_map[i]) >= 0)
+			matches += find_subplans_for_params_recurse(prunestate,
+														&prunestate->partprunedata[partidx],
+														initial_prune, validsubplans);
 		else
 		{
-			int			partidx = pprune->subpart_map[i];
-
-			if (partidx != -1)
-				find_subplans_for_params_recurse(prunestate,
-												 &prunestate->partprunedata[partidx],
-												 allparams, validsubplans);
-			else
-			{
-				/*
-				 * This could only happen if clauses used in planning where
-				 * more restrictive than those used here, or if the maps are
-				 * somehow corrupt.
-				 */
-				elog(ERROR, "partition missing from subplans");
-			}
+			/* Shouldn't happen */
+			elog(ERROR, "partition missing from subplans");
 		}
 	}
+	return matches;
 }
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 6bc3e470bf..707a3e0e4b 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -138,11 +138,8 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 		prunestate = ExecSetupPartitionPruneState(&appendstate->ps,
 												  node->part_prune_infos);
 
-		/*
-		 * When there are external params matching the partition key we may be
-		 * able to prune away Append subplans now.
-		 */
-		if (!bms_is_empty(prunestate->extparams))
+		/* Perform an initial partition prune, if required. */
+		if (prunestate->do_initial_prune)
 		{
 			/* Determine which subplans match the external params */
 			validsubplans = ExecFindInitialMatchingSubPlans(prunestate,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7c045a7afe..4c6bddaa35 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1190,6 +1190,29 @@ _copyPlanInvalItem(const PlanInvalItem *from)
 	return newnode;
 }
 
+/*
+ * _copyPartitionPruneInfo
+ */
+static PartitionPruneInfo *
+_copyPartitionPruneInfo(const PartitionPruneInfo *from)
+{
+	PartitionPruneInfo *newnode = makeNode(PartitionPruneInfo);
+
+	COPY_SCALAR_FIELD(reloid);
+	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(hasexecparam, from->nexprs * sizeof(bool));
+	COPY_SCALAR_FIELD(do_initial_prune);
+	COPY_SCALAR_FIELD(do_exec_prune);
+	COPY_BITMAPSET_FIELD(execparams);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					   primnodes.h copy functions
  * ****************************************************************
@@ -2166,23 +2189,6 @@ _copyPartitionPruneStepCombine(const PartitionPruneStepCombine *from)
 	return newnode;
 }
 
-static PartitionPruneInfo *
-_copyPartitionPruneInfo(const PartitionPruneInfo *from)
-{
-	PartitionPruneInfo *newnode = makeNode(PartitionPruneInfo);
-
-	COPY_SCALAR_FIELD(reloid);
-	COPY_NODE_FIELD(pruning_steps);
-	COPY_BITMAPSET_FIELD(present_parts);
-	COPY_SCALAR_FIELD(nparts);
-	COPY_POINTER_FIELD(subnode_map, from->nparts * sizeof(int));
-	COPY_POINTER_FIELD(subpart_map, from->nparts * sizeof(int));
-	COPY_BITMAPSET_FIELD(extparams);
-	COPY_BITMAPSET_FIELD(execparams);
-
-	return newnode;
-}
-
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4904,6 +4910,9 @@ copyObjectImpl(const void *from)
 		case T_PlanInvalItem:
 			retval = _copyPlanInvalItem(from);
 			break;
+		case T_PartitionPruneInfo:
+			retval = _copyPartitionPruneInfo(from);
+			break;
 
 			/*
 			 * PRIMITIVE NODES
@@ -5089,9 +5098,6 @@ copyObjectImpl(const void *from)
 		case T_PlaceHolderInfo:
 			retval = _copyPlaceHolderInfo(from);
 			break;
-		case T_PartitionPruneInfo:
-			retval = _copyPartitionPruneInfo(from);
-			break;
 
 			/*
 			 * VALUE NODES
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 610f9edaf5..b6b53a2de3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1019,6 +1019,36 @@ _outPlanInvalItem(StringInfo str, const PlanInvalItem *node)
 	WRITE_UINT_FIELD(hashValue);
 }
 
+static void
+_outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
+{
+	int			i;
+
+	WRITE_NODE_TYPE("PARTITIONPRUNEINFO");
+
+	WRITE_OID_FIELD(reloid);
+	WRITE_NODE_FIELD(pruning_steps);
+	WRITE_BITMAPSET_FIELD(present_parts);
+	WRITE_INT_FIELD(nparts);
+	WRITE_INT_FIELD(nexprs);
+
+	appendStringInfoString(str, " :subplan_map");
+	for (i = 0; i < node->nparts; i++)
+		appendStringInfo(str, " %d", node->subplan_map[i]);
+
+	appendStringInfoString(str, " :subpart_map");
+	for (i = 0; i < node->nparts; i++)
+		appendStringInfo(str, " %d", node->subpart_map[i]);
+
+	appendStringInfoString(str, " :hasexecparam");
+	for (i = 0; i < node->nexprs; i++)
+		appendStringInfo(str, " %s", booltostr(node->hasexecparam[i]));
+
+	WRITE_BOOL_FIELD(do_initial_prune);
+	WRITE_BOOL_FIELD(do_exec_prune);
+	WRITE_BITMAPSET_FIELD(execparams);
+}
+
 /*****************************************************************************
  *
  *	Stuff from primnodes.h.
@@ -1731,30 +1761,6 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
-static void
-_outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
-{
-	int			i;
-
-	WRITE_NODE_TYPE("PARTITIONPRUNEINFO");
-
-	WRITE_OID_FIELD(reloid);
-	WRITE_NODE_FIELD(pruning_steps);
-	WRITE_BITMAPSET_FIELD(present_parts);
-	WRITE_INT_FIELD(nparts);
-
-	appendStringInfoString(str, " :subnode_map");
-	for (i = 0; i < node->nparts; i++)
-		appendStringInfo(str, " %d", node->subnode_map[i]);
-
-	appendStringInfoString(str, " :subpart_map");
-	for (i = 0; i < node->nparts; i++)
-		appendStringInfo(str, " %d", node->subpart_map[i]);
-
-	WRITE_BITMAPSET_FIELD(extparams);
-	WRITE_BITMAPSET_FIELD(execparams);
-}
-
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3824,6 +3830,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PlanInvalItem:
 				_outPlanInvalItem(str, obj);
 				break;
+			case T_PartitionPruneInfo:
+				_outPartitionPruneInfo(str, obj);
+				break;
 			case T_Alias:
 				_outAlias(str, obj);
 				break;
@@ -3983,9 +3992,6 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionPruneStepCombine:
 				_outPartitionPruneStepCombine(str, obj);
 				break;
-			case T_PartitionPruneInfo:
-				_outPartitionPruneInfo(str, obj);
-				break;
 			case T_Path:
 				_outPath(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 2826cec2f8..18bbf37e14 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1354,23 +1354,6 @@ _readPartitionPruneStepCombine(void)
 	READ_DONE();
 }
 
-static PartitionPruneInfo *
-_readPartitionPruneInfo(void)
-{
-	READ_LOCALS(PartitionPruneInfo);
-
-	READ_OID_FIELD(reloid);
-	READ_NODE_FIELD(pruning_steps);
-	READ_BITMAPSET_FIELD(present_parts);
-	READ_INT_FIELD(nparts);
-	READ_INT_ARRAY(subnode_map, local_node->nparts);
-	READ_INT_ARRAY(subpart_map, local_node->nparts);
-	READ_BITMAPSET_FIELD(extparams);
-	READ_BITMAPSET_FIELD(execparams);
-
-	READ_DONE();
-}
-
 /*
  *	Stuff from parsenodes.h.
  */
@@ -2376,6 +2359,29 @@ _readPlanInvalItem(void)
 	READ_DONE();
 }
 
+/*
+ * _readPartitionPruneInfo
+ */
+static PartitionPruneInfo *
+_readPartitionPruneInfo(void)
+{
+	READ_LOCALS(PartitionPruneInfo);
+
+	READ_OID_FIELD(reloid);
+	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_BOOL_ARRAY(hasexecparam, local_node->nexprs);
+	READ_BOOL_FIELD(do_initial_prune);
+	READ_BOOL_FIELD(do_exec_prune);
+	READ_BITMAPSET_FIELD(execparams);
+
+	READ_DONE();
+}
+
 /*
  * _readSubPlan
  */
@@ -2620,8 +2626,6 @@ parseNodeString(void)
 		return_value = _readPartitionPruneStepOp();
 	else if (MATCH("PARTITIONPRUNESTEPCOMBINE", 25))
 		return_value = _readPartitionPruneStepCombine();
-	else if (MATCH("PARTITIONPRUNEINFO", 18))
-		return_value = _readPartitionPruneInfo();
 	else if (MATCH("RTE", 3))
 		return_value = _readRangeTblEntry();
 	else if (MATCH("RANGETBLFUNCTION", 16))
@@ -2724,6 +2728,8 @@ parseNodeString(void)
 		return_value = _readPlanRowMark();
 	else if (MATCH("PLANINVALITEM", 13))
 		return_value = _readPlanInvalItem();
+	else if (MATCH("PARTITIONPRUNEINFO", 18))
+		return_value = _readPartitionPruneInfo();
 	else if (MATCH("SUBPLAN", 7))
 		return_value = _readSubPlan();
 	else if (MATCH("ALTERNATIVESUBPLAN", 18))
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 58ec2a684d..640105ddf2 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -25,7 +25,7 @@
  * the outputs of some other steps using the appropriate combination method.
  * All steps that are constructed are executed in succession such that for any
  * "combine" step, all of the steps whose output it depends on are executed
- * first and their ouput preserved.
+ * first and their output preserved.
  *
  * See gen_partprune_steps_internal() for more details on step generation.
  *
@@ -53,6 +53,7 @@
 #include "optimizer/planner.h"
 #include "optimizer/predtest.h"
 #include "optimizer/prep.h"
+#include "optimizer/var.h"
 #include "partitioning/partprune.h"
 #include "partitioning/partbounds.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,14 @@ typedef struct PruneStepResult
 	bool		scan_null;		/* Scan the partition for NULL values? */
 } PruneStepResult;
 
+/*
+ * expression_tree_walker context struct for gathering the paramids in an
+ * expression.
+ */
+typedef struct PullParamContext
+{
+	Bitmapset  *params;
+} PullParamContext;
 
 static List *gen_partprune_steps(RelOptInfo *rel, List *clauses,
 					bool *contradictory);
@@ -162,7 +171,10 @@ static PruneStepResult *get_matching_list_bounds(PartitionPruneContext *context,
 static PruneStepResult *get_matching_range_bounds(PartitionPruneContext *context,
 						  StrategyNumber opstrategy, Datum *values, int nvalues,
 						  FmgrInfo *partsupfunc, Bitmapset *nullkeys);
-static bool pull_partkey_params(PartitionPruneInfo *pinfo, List *steps);
+static Bitmapset *pull_exec_paramids(Expr *expr);
+static bool pull_exec_paramids_walker(Node *node, PullParamContext *context);
+static bool analyze_partkey_exprs(PartitionPruneInfo *pinfo, List *steps,
+					  int partnatts);
 static PruneStepResult *perform_pruning_base_step(PartitionPruneContext *context,
 						  PartitionPruneStepOp *opstep);
 static PruneStepResult *perform_pruning_combine_step(PartitionPruneContext *context,
@@ -180,7 +192,7 @@ static bool partkey_datum_from_expr(PartitionPruneContext *context,
  *		pruning to take place.
  *
  * Here we generate partition pruning steps for 'prunequal' and also build a
- * data stucture which allows mapping of partition indexes into 'subpaths'
+ * data structure which allows mapping of partition indexes into 'subpaths'
  * indexes.
  *
  * If no Params were found to match the partition key in any of the
@@ -194,16 +206,16 @@ make_partition_pruneinfo(PlannerInfo *root, List *partition_rels,
 	RelOptInfo *targetpart = NULL;
 	ListCell   *lc;
 	List	   *pinfolist = NIL;
-	int		   *relid_subnode_map;
+	int		   *relid_subplan_map;
 	int		   *relid_subpart_map;
 	int			i;
-	bool		gotparam = false;
+	bool		doruntimeprune = false;
 
 	/*
 	 * Allocate two arrays to store the 1-based indexes of the 'subpaths' and
 	 * 'partitioned_rels' by relid.
 	 */
-	relid_subnode_map = palloc0(sizeof(int) * root->simple_rel_array_size);
+	relid_subplan_map = palloc0(sizeof(int) * root->simple_rel_array_size);
 	relid_subpart_map = palloc0(sizeof(int) * root->simple_rel_array_size);
 
 	i = 1;
@@ -215,7 +227,7 @@ make_partition_pruneinfo(PlannerInfo *root, List *partition_rels,
 		Assert(IS_SIMPLE_REL(pathrel));
 		Assert(pathrel->relid < root->simple_rel_array_size);
 
-		relid_subnode_map[pathrel->relid] = i++;
+		relid_subplan_map[pathrel->relid] = i++;
 	}
 
 	/* Likewise for the partition_rels */
@@ -238,7 +250,8 @@ make_partition_pruneinfo(PlannerInfo *root, List *partition_rels,
 		RangeTblEntry *rte;
 		Bitmapset  *present_parts;
 		int			nparts = subpart->nparts;
-		int		   *subnode_map;
+		int			partnatts = subpart->part_scheme->partnatts;
+		int		   *subplan_map;
 		int		   *subpart_map;
 		List	   *partprunequal;
 		List	   *pruning_steps;
@@ -284,7 +297,7 @@ make_partition_pruneinfo(PlannerInfo *root, List *partition_rels,
 			return NIL;
 		}
 
-		subnode_map = (int *) palloc(nparts * sizeof(int));
+		subplan_map = (int *) palloc(nparts * sizeof(int));
 		subpart_map = (int *) palloc(nparts * sizeof(int));
 		present_parts = NULL;
 
@@ -297,10 +310,10 @@ make_partition_pruneinfo(PlannerInfo *root, List *partition_rels,
 		for (i = 0; i < nparts; i++)
 		{
 			RelOptInfo *partrel = subpart->part_rels[i];
-			int			subnodeidx = relid_subnode_map[partrel->relid] - 1;
+			int			subnodeidx = relid_subplan_map[partrel->relid] - 1;
 			int			subpartidx = relid_subpart_map[partrel->relid] - 1;
 
-			subnode_map[i] = subnodeidx;
+			subplan_map[i] = subnodeidx;
 			subpart_map[i] = subpartidx;
 
 			/*
@@ -320,32 +333,27 @@ make_partition_pruneinfo(PlannerInfo *root, List *partition_rels,
 		pinfo->pruning_steps = pruning_steps;
 		pinfo->present_parts = present_parts;
 		pinfo->nparts = nparts;
-		pinfo->extparams = NULL;
-		pinfo->execparams = NULL;
-		pinfo->subnode_map = subnode_map;
+		pinfo->nexprs = list_length(pruning_steps) * partnatts;
+		pinfo->subplan_map = subplan_map;
 		pinfo->subpart_map = subpart_map;
 
 		/*
-		 * Extract Params matching partition key and record if we got any.
-		 * We'll not bother enabling run-time pruning if no params matched the
-		 * partition key at any level of partitioning.
+		 * Determine when run-time pruning needs to be performed for this
+		 * partitioned table.
 		 */
-		gotparam |= pull_partkey_params(pinfo, pruning_steps);
+		doruntimeprune |= analyze_partkey_exprs(pinfo, pruning_steps,
+												partnatts);
 
 		pinfolist = lappend(pinfolist, pinfo);
 	}
 
-	pfree(relid_subnode_map);
+	pfree(relid_subplan_map);
 	pfree(relid_subpart_map);
 
-	if (gotparam)
+	if (doruntimeprune)
 		return pinfolist;
 
-	/*
-	 * If no Params were found to match the partition key on any of the
-	 * partitioned relations then there's no point doing any run-time
-	 * partition pruning.
-	 */
+	/* No run-time pruning required. */
 	return NIL;
 }
 
@@ -444,9 +452,10 @@ prune_append_rel_partitions(RelOptInfo *rel)
 	context.boundinfo = rel->boundinfo;
 
 	/* Not valid when being called from the planner */
+	context.evalexecparams = false;
 	context.planstate = NULL;
-	context.safeparams = NULL;
 	context.exprstates = NULL;
+	context.exprhasexecparam = NULL;
 
 	/* Actual pruning happens here. */
 	partindexes = get_matching_partitions(&context, pruning_steps);
@@ -1478,6 +1487,10 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		if (contain_volatile_functions((Node *) expr))
 			return PARTCLAUSE_UNSUPPORTED;
 
+		/* We can't prune using an expression with Vars. */
+		if (contain_var_clause((Node *) expr))
+			return PARTCLAUSE_UNSUPPORTED;
+
 		/*
 		 * Determine the input types of the operator we're considering.
 		 *
@@ -1655,7 +1668,7 @@ match_clause_to_partition_key(RelOptInfo *rel,
 					return PARTCLAUSE_UNSUPPORTED;
 			}
 			else
-				return PARTCLAUSE_UNSUPPORTED; /* no useful negator */
+				return PARTCLAUSE_UNSUPPORTED;	/* no useful negator */
 		}
 
 		/*
@@ -2683,54 +2696,97 @@ get_matching_range_bounds(PartitionPruneContext *context,
 }
 
 /*
- * pull_partkey_params
- *		Loop through each pruning step and record each external and exec
- *		Params being compared to the partition keys.
+ * pull_exec_paramids
+ *		Returns a Bitmapset containing the paramids of each Param with
+ *		paramkind = PARAM_EXEC in 'expr'.
+ */
+static Bitmapset *
+pull_exec_paramids(Expr *expr)
+{
+	PullParamContext context;
+
+	context.params = NULL;
+
+	pull_exec_paramids_walker((Node *) expr, &context);
+
+	return context.params;
+}
+
+static bool
+pull_exec_paramids_walker(Node *node, PullParamContext *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Param))
+	{
+		Param	   *param = (Param *) node;
+
+		if (param->paramkind == PARAM_EXEC)
+			context->params = bms_add_member(context->params, param->paramid);
+		return false;
+	}
+	return expression_tree_walker(node, pull_exec_paramids_walker,
+								  (void *) context);
+}
+
+/*
+ * analyze_partkey_exprs
+ *		Loop through each pruning steps recording which one are comparing exec
+ *		params to the partition key.
+ *
+ * Returns true if run-time partition pruning should be attempted for this
+ * partitioned table.
  */
 static bool
-pull_partkey_params(PartitionPruneInfo *pinfo, List *steps)
+analyze_partkey_exprs(PartitionPruneInfo *pinfo, List *steps, int partnatts)
 {
 	ListCell   *lc;
-	bool		gotone = false;
+	bool		doruntimeprune = false;
+
+	pinfo->hasexecparam = palloc0(sizeof(bool) * pinfo->nexprs);
+	pinfo->execparams = NULL;
+	pinfo->do_initial_prune = false;
+	pinfo->do_exec_prune = false;
 
 	foreach(lc, steps)
 	{
-		PartitionPruneStepOp *stepop = lfirst(lc);
+		PartitionPruneStepOp *step = lfirst(lc);
 		ListCell   *lc2;
+		int			keyno;
 
-		if (!IsA(stepop, PartitionPruneStepOp))
+		if (!IsA(step, PartitionPruneStepOp))
 			continue;
 
-		foreach(lc2, stepop->exprs)
+		keyno = 0;
+		foreach(lc2, step->exprs)
 		{
 			Expr	   *expr = lfirst(lc2);
 
-			if (IsA(expr, Param))
+			if (!IsA(expr, Const))
 			{
-				Param	   *param = (Param *) expr;
-
-				switch (param->paramkind)
-				{
-					case PARAM_EXTERN:
-						pinfo->extparams = bms_add_member(pinfo->extparams,
-														  param->paramid);
-						break;
-					case PARAM_EXEC:
-						pinfo->execparams = bms_add_member(pinfo->execparams,
-														   param->paramid);
-						break;
+				Bitmapset  *execparams = pull_exec_paramids(expr);
+				bool		hasexecparams;
+				int			stateidx = PruneCxtStateIdx(partnatts,
+														step->step.step_id,
+														keyno);
+
+				hasexecparams = !bms_is_empty(execparams);
+				pinfo->hasexecparam[stateidx] = hasexecparams;
+				pinfo->execparams = bms_add_members(pinfo->execparams,
+													execparams);
+
+				if (!hasexecparams)
+					pinfo->do_initial_prune = true;
+				else
+					pinfo->do_exec_prune = true;
 
-					default:
-						elog(ERROR, "unrecognized paramkind: %d",
-							 (int) param->paramkind);
-						break;
-				}
-				gotone = true;
+				doruntimeprune = true;
 			}
+			keyno++;
 		}
 	}
 
-	return gotone;
+	return doruntimeprune;
 }
 
 /*
@@ -3031,37 +3087,43 @@ static bool
 partkey_datum_from_expr(PartitionPruneContext *context,
 						Expr *expr, int stateidx, Datum *value)
 {
-	switch (nodeTag(expr))
+	if (IsA(expr, Const))
 	{
-		case T_Const:
-			*value = ((Const *) expr)->constvalue;
-			return true;
-
-		case T_Param:
+		*value = ((Const *) expr)->constvalue;
+		return true;
+	}
+	else
+	{
+		/*
+		 * When called from the executor we'll have a valid planstate so we
+		 * may be able to evaluate expressions which could not be folded to
+		 * constants during planning.  Since run-time pruning can occur both
+		 * during initialization of the executor and while it's running, we
+		 * must be careful not to attempt to evaluate expressions containing
+		 * exec params during initialization of the executor.
+		 */
+		if (context->planstate &&
+			(context->evalexecparams ||
+			 !context->exprhasexecparam[stateidx]))
+		{
+			ExprState  *exprstate;
+			ExprContext *ectx;
+			bool		isNull;
 
-			/*
-			 * When being called from the executor we may be able to evaluate
-			 * the Param's value.
-			 */
-			if (context->planstate &&
-				bms_is_member(((Param *) expr)->paramid, context->safeparams))
-			{
-				ExprState  *exprstate;
-				ExprContext *ectx;
-				bool		isNull;
+			/* Exprs with volatile functions shouldn't make it here */
+			Assert(!contain_volatile_functions((Node *) expr));
 
-				exprstate = context->exprstates[stateidx];
-				ectx = context->planstate->ps_ExprContext;
-				*value = ExecEvalExprSwitchContext(exprstate, ectx, &isNull);
-				if (isNull)
-					return false;
+			/* Exprs with Vars shouldn't make it here either */
+			Assert(!contain_var_clause((Node *) expr));
 
-				return true;
-			}
-			break;
+			exprstate = context->exprstates[stateidx];
+			ectx = context->planstate->ps_ExprContext;
+			*value = ExecEvalExprSwitchContext(exprstate, ectx, &isNull);
+			if (isNull)
+				return false;
 
-		default:
-			break;
+			return true;
+		}
 	}
 
 	return false;
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index fc6e9574e3..ae850f4d6e 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -121,9 +121,9 @@ typedef struct PartitionTupleRouting
  * bypass certain subnodes when we have proofs that indicate that no tuple
  * matching the 'pruning_steps' will be found within.
  *
- * subnode_map					An array containing the subnode index which
+ * subplan_map					An array containing the subplan index which
  *								matches this partition index, or -1 if the
- *								subnode has been pruned already.
+ *								subplan has been pruned already.
  * subpart_map					An array containing the offset into the
  *								'partprunedata' array in PartitionPruning, or
  *								-1 if there is no such element in that array.
@@ -133,20 +133,24 @@ typedef struct PartitionTupleRouting
  *								the partition pruning code.
  * pruning_steps				Contains a list of PartitionPruneStep used to
  *								perform the actual pruning.
- * extparams					Contains paramids of external params found
+ * execparams					Contains paramids of exec params found
  *								matching partition keys in 'pruning_steps'.
- * allparams					As 'extparams' but also including exec params.
+ * do_initial_prune				true if pruning should be performed during
+ *								executor startup.
+ * do_exec_prune				true if pruning should be performed during
+ *								executor run.
  *-----------------------
  */
 typedef struct PartitionPruningData
 {
-	int		   *subnode_map;
+	int		   *subplan_map;
 	int		   *subpart_map;
 	Bitmapset  *present_parts;
 	PartitionPruneContext context;
 	List	   *pruning_steps;
-	Bitmapset  *extparams;
-	Bitmapset  *allparams;
+	Bitmapset  *execparams;
+	bool		do_initial_prune;
+	bool		do_exec_prune;
 } PartitionPruningData;
 
 /*-----------------------
@@ -159,28 +163,28 @@ typedef struct PartitionPruningData
  * the clauses being unable to match to any tuple that the subnode could
  * possibly produce.
  *
- * partprunedata		Array of PartitionPruningData for the node's target
- *						partitioned relation. First element contains the
- *						details for the target partitioned table.
  * num_partprunedata	Number of items in 'partprunedata' array.
+ * do_initial_prune		true if pruning should be performed during executor
+ *						startup.
  * prune_context		A memory context which can be used to call the query
  *						planner's partition prune functions.
- * extparams			All PARAM_EXTERN paramids which were found to match a
+ * execparams			All PARAM_EXEC paramids which were found to match a
  *						partition key in each of the contained
- *						PartitionPruningData structs.
- * execparams			As above but for PARAM_EXEC.
- * allparams			Union of 'extparams' and 'execparams', saved to avoid
- *						recalculation.
+ *						PartitionPruningData structs.  Pruning must be done
+ *						again each time the value of one of these parameters
+ *						changes.
+ * partprunedata		Array of PartitionPruningData for the node's target
+ *						partitioned relation. First element contains the
+ *						details for the target partitioned table.
  *-----------------------
  */
 typedef struct PartitionPruneState
 {
-	PartitionPruningData *partprunedata;
 	int			num_partprunedata;
+	bool		do_initial_prune;
 	MemoryContext prune_context;
-	Bitmapset  *extparams;
 	Bitmapset  *execparams;
-	Bitmapset  *allparams;
+	PartitionPruningData partprunedata[FLEXIBLE_ARRAY_MEMBER];
 } PartitionPruneState;
 
 extern PartitionTupleRouting *ExecSetupPartitionTupleRouting(ModifyTableState *mtstate,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index adb159a6da..120b1c7a7b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -88,6 +88,7 @@ typedef enum NodeTag
 	T_NestLoopParam,
 	T_PlanRowMark,
 	T_PlanInvalItem,
+	T_PartitionPruneInfo,
 
 	/*
 	 * TAGS FOR PLAN STATE NODES (execnodes.h)
@@ -195,7 +196,6 @@ typedef enum NodeTag
 	T_PartitionPruneStep,
 	T_PartitionPruneStepOp,
 	T_PartitionPruneStepCombine,
-	T_PartitionPruneInfo,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index f2dda82e66..0ff14c96f3 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -1062,4 +1062,35 @@ typedef struct PlanInvalItem
 	uint32		hashValue;		/* hash value of object's cache lookup key */
 } PlanInvalItem;
 
+/*
+ * PartitionPruneInfo - Details required to allow the executor to prune
+ * partitions.
+ *
+ * Here we store mapping details to allow translation of a partitioned table's
+ * index as returned by the partition pruning code into subplan indexes for
+ * node types which support arbitrary numbers of subplans, such as Append.
+ * We also store various details to give indication to the executor when it
+ * should be performing partition pruning.
+ */
+typedef struct PartitionPruneInfo
+{
+	NodeTag		type;
+	Oid			reloid;			/* Oid of partition rel */
+	List	   *pruning_steps;	/* List of PartitionPruneStep */
+	Bitmapset  *present_parts;	/* Indexes of all partitions which subnodes
+								 * are present for. */
+	int			nparts;			/* The length of the following two arrays */
+	int			nexprs;			/* Size of hasexecparam array */
+	int		   *subplan_map;	/* subplan index by partition id, or -1 */
+	int		   *subpart_map;	/* subpart index by partition id, or -1 */
+	bool	   *hasexecparam;	/* true if corresponding pruning_step has an
+								 * exec Param in the Expr being compared to
+								 * the partition key. */
+	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  *execparams;		/* All exec paramids seen in prunesteps */
+} PartitionPruneInfo;
+
 #endif							/* PLANNODES_H */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f90aa7b2a1..ff5c4ff8e4 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1581,27 +1581,4 @@ typedef struct PartitionPruneStepCombine
 	List	   *source_stepids;
 } PartitionPruneStepCombine;
 
-/*----------
- * PartitionPruneInfo - Details required to allow the executor to prune
- * partitions.
- *
- * Here we store mapping details to allow translation of a partitioned table's
- * index into subnode indexes for node types which support arbitrary numbers
- * of sub nodes, such as Append.
- *----------
- */
-typedef struct PartitionPruneInfo
-{
-	NodeTag		type;
-	Oid			reloid;			/* Oid of partition rel */
-	List	   *pruning_steps;	/* List of PartitionPruneStep */
-	Bitmapset  *present_parts;	/* Indexes of all partitions which subnodes
-								 * are present for. */
-	int			nparts;			/* The length of the following two arrays */
-	int		   *subnode_map;	/* subnode index by partition id, or -1 */
-	int		   *subpart_map;	/* subpart index by partition id, or -1 */
-	Bitmapset  *extparams;		/* All external paramids seen in prunesteps */
-	Bitmapset  *execparams;		/* All exec paramids seen in prunesteps */
-} PartitionPruneInfo;
-
 #endif							/* PRIMNODES_H */
diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h
index 3d114b4c71..4ac8cf5552 100644
--- a/src/include/partitioning/partprune.h
+++ b/src/include/partitioning/partprune.h
@@ -36,6 +36,9 @@ typedef struct PartitionPruneContext
 	/* Number of partitions */
 	int			nparts;
 
+	/* true if it's safe to evaluate exec params */
+	bool		evalexecparams;
+
 	/* Partition boundary info */
 	PartitionBoundInfo boundinfo;
 
@@ -45,18 +48,15 @@ typedef struct PartitionPruneContext
 	 */
 	PlanState  *planstate;
 
-	/*
-	 * Parameters that are safe to be used for partition pruning. execparams
-	 * are not safe to use until the executor is running.
-	 */
-	Bitmapset  *safeparams;
-
 	/*
 	 * Array of ExprStates, indexed as per PruneCtxStateIdx; one for each
 	 * partkey in each pruning step.  Allocated if planstate is non-NULL,
 	 * otherwise NULL.
 	 */
 	ExprState **exprstates;
+
+	/* true if corresponding 'exprstate' expression contains an exec param */
+	bool	   *exprhasexecparam;
 } PartitionPruneContext;
 
 #define PruneCxtStateIdx(partnatts, step_id, keyno) \
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index cf331e79c1..64b4e933d6 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -1835,6 +1835,54 @@ fetch backward all from cur;
 (2 rows)
 
 commit;
+begin;
+-- Test run-time pruning using stable functions
+create function list_part_fn(int) returns int as $$ begin return $1; end;$$ language plpgsql stable;
+-- Ensure pruning works using a stable function containing no Vars
+explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Append (actual rows=1 loops=1)
+   Subplans Removed: 3
+   ->  Seq Scan on list_part1 (actual rows=1 loops=1)
+         Filter: (a = list_part_fn(1))
+(4 rows)
+
+-- Ensure pruning does not take place when the function contains a Var parameter
+explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(a);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Append (actual rows=4 loops=1)
+   ->  Seq Scan on list_part1 (actual rows=1 loops=1)
+         Filter: (a = list_part_fn(a))
+   ->  Seq Scan on list_part2 (actual rows=1 loops=1)
+         Filter: (a = list_part_fn(a))
+   ->  Seq Scan on list_part3 (actual rows=1 loops=1)
+         Filter: (a = list_part_fn(a))
+   ->  Seq Scan on list_part4 (actual rows=1 loops=1)
+         Filter: (a = list_part_fn(a))
+(9 rows)
+
+-- Ensure pruning does not take place when the expression contains a Var.
+explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1) + a;
+                      QUERY PLAN                      
+------------------------------------------------------
+ Append (actual rows=0 loops=1)
+   ->  Seq Scan on list_part1 (actual rows=0 loops=1)
+         Filter: (a = (list_part_fn(1) + a))
+         Rows Removed by Filter: 1
+   ->  Seq Scan on list_part2 (actual rows=0 loops=1)
+         Filter: (a = (list_part_fn(1) + a))
+         Rows Removed by Filter: 1
+   ->  Seq Scan on list_part3 (actual rows=0 loops=1)
+         Filter: (a = (list_part_fn(1) + a))
+         Rows Removed by Filter: 1
+   ->  Seq Scan on list_part4 (actual rows=0 loops=1)
+         Filter: (a = (list_part_fn(1) + a))
+         Rows Removed by Filter: 1
+(13 rows)
+
+rollback;
 drop table list_part;
 -- Parallel append
 -- Suppress the number of loops each parallel node runs for.  This is because
@@ -2079,6 +2127,40 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
 (27 rows)
 
+-- Ensure the same partitions are pruned when we make the nested loop
+-- parameter an Expr rather than a plain Param.
+select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a + 0 where a.a in(0, 0, 1)');
+                                      explain_parallel_append                                      
+---------------------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=1 loops=1)
+   ->  Gather (actual rows=2 loops=1)
+         Workers Planned: 1
+         Workers Launched: 1
+         ->  Partial Aggregate (actual rows=1 loops=2)
+               ->  Nested Loop (actual rows=0 loops=2)
+                     ->  Parallel Seq Scan on lprt_a a (actual rows=51 loops=N)
+                           Filter: (a = ANY ('{0,0,1}'::integer[]))
+                     ->  Append (actual rows=0 loops=102)
+                           ->  Index Scan using ab_a1_b1_a_idx on ab_a1_b1 (actual rows=0 loops=2)
+                                 Index Cond: (a = (a.a + 0))
+                           ->  Index Scan using ab_a1_b2_a_idx on ab_a1_b2 (actual rows=0 loops=2)
+                                 Index Cond: (a = (a.a + 0))
+                           ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 (actual rows=0 loops=2)
+                                 Index Cond: (a = (a.a + 0))
+                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed)
+                                 Index Cond: (a = (a.a + 0))
+                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed)
+                                 Index Cond: (a = (a.a + 0))
+                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed)
+                                 Index Cond: (a = (a.a + 0))
+                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 (never executed)
+                                 Index Cond: (a = (a.a + 0))
+                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 (never executed)
+                                 Index Cond: (a = (a.a + 0))
+                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 (never executed)
+                                 Index Cond: (a = (a.a + 0))
+(27 rows)
+
 insert into lprt_a values(3),(3);
 select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 3)');
                                       explain_parallel_append                                      
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index 1464f4dcd9..b6681fa44c 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -396,6 +396,22 @@ fetch backward all from cur;
 
 commit;
 
+begin;
+
+-- Test run-time pruning using stable functions
+create function list_part_fn(int) returns int as $$ begin return $1; end;$$ language plpgsql stable;
+
+-- Ensure pruning works using a stable function containing no Vars
+explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1);
+
+-- Ensure pruning does not take place when the function contains a Var parameter
+explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(a);
+
+-- Ensure pruning does not take place when the expression contains a Var.
+explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1) + a;
+
+rollback;
+
 drop table list_part;
 
 -- Parallel append
@@ -486,6 +502,10 @@ set enable_mergejoin = 0;
 
 select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(0, 0, 1)');
 
+-- Ensure the same partitions are pruned when we make the nested loop
+-- parameter an Expr rather than a plain Param.
+select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a + 0 where a.a in(0, 0, 1)');
+
 insert into lprt_a values(3),(3);
 
 select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 3)');
