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