diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd..50eb019da5 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1042,7 +1042,8 @@ CopyFrom(CopyFromState cstate)
 				 */
 				if (resultRelInfo->ri_RelationDesc->rd_rel->relispartition &&
 					(proute == NULL || has_before_insert_row_trig))
-					ExecPartitionCheck(resultRelInfo, myslot, estate, true);
+					ExecPartitionCheck(resultRelInfo, myslot, estate, NULL,
+									   true);
 
 				/* Store the slot in the multi-insert buffer, when enabled. */
 				if (insertMethod == CIM_MULTI || leafpart_use_multi_insert)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 07c73f39de..8fc647cd0e 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2288,7 +2288,7 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 			 * longer fits the partition.  Verify that.
 			 */
 			if (trigger->tgisclone &&
-				!ExecPartitionCheck(relinfo, slot, estate, false))
+				!ExecPartitionCheck(relinfo, slot, estate, NULL, false))
 				ereport(ERROR,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported"),
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..8ddbc6d0c2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -44,6 +44,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_publication.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
@@ -52,6 +53,8 @@
 #include "foreign/fdwapi.h"
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "miscadmin.h"
 #include "parser/parsetree.h"
 #include "storage/bufmgr.h"
@@ -1686,6 +1689,32 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * Replaces the occurrence of cxt->matchexpr in the expression tree given by
+ * 'node' by an OUTER var with provided attribute number.
+ */
+typedef struct
+{
+	Expr	   *matchexpr;
+	AttrNumber	varattno;
+} replace_partexpr_with_dummy_var_context;
+
+static Node *
+replace_partexpr_with_dummy_var(Node *node,
+								replace_partexpr_with_dummy_var_context *cxt)
+{
+	if (node == NULL)
+		return NULL;
+
+	if (equal(node, cxt->matchexpr))
+		return (Node *) makeVar(OUTER_VAR, cxt->varattno,
+								exprType(node), exprTypmod(node),
+								exprCollation(node), 0);
+
+	return expression_tree_mutator(node, replace_partexpr_with_dummy_var,
+								   (void *) cxt);
+}
+
 /*
  * ExecPartitionCheck --- check that tuple meets the partition constraint.
  *
@@ -1695,7 +1724,7 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
  */
 bool
 ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
-				   EState *estate, bool emitError)
+				   EState *estate, Relation parentrel, bool emitError)
 {
 	ExprContext *econtext;
 	bool		success;
@@ -1716,6 +1745,50 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
 		List	   *qual = RelationGetPartitionQual(resultRelInfo->ri_RelationDesc);
 
+		/*
+		 * If we have been passed the parent relation, optimize the evaluation
+		 * of partition key expressions.  The way we do that is by replacing
+		 * any occurrences of the individual expressions in this relation's
+		 * partition constraint by dummy Vars marked as coming from the
+		 * "OUTER" relation.  Then when actually executing such modified
+		 * partition constraint tree, we feed the actual partition expression
+		 * values via econtext->ecxt_outertuple; see below.
+		 */
+		if (parentrel)
+		{
+			replace_partexpr_with_dummy_var_context cxt;
+			List   *partexprs = RelationGetPartitionKey(parentrel)->partexprs;
+			ListCell *lc;
+			AttrNumber attrno = 1;
+			TupleDesc	partexprs_tupdesc;
+
+			partexprs_tupdesc = CreateTemplateTupleDesc(list_length(partexprs));
+
+			partexprs = map_partition_varattnos(partexprs, 1,
+												resultRelInfo->ri_RelationDesc,
+												parentrel);
+			foreach(lc, partexprs)
+			{
+				Expr   *expr = lfirst(lc);
+
+				cxt.matchexpr = expr;
+				cxt.varattno = attrno;
+				qual = (List *) replace_partexpr_with_dummy_var((Node *) qual,
+																&cxt);
+
+				resultRelInfo->ri_partitionKeyExprs =
+					lappend(resultRelInfo->ri_partitionKeyExprs,
+							ExecPrepareExpr(expr, estate));
+				TupleDescInitEntry(partexprs_tupdesc, attrno, NULL,
+								   exprType((Node *) expr),
+								   exprTypmod((Node *) expr), 0);
+				attrno++;
+			}
+
+			resultRelInfo->ri_partitionKeyExprsSlot =
+				ExecInitExtraTupleSlot(estate, partexprs_tupdesc, &TTSOpsVirtual);
+		}
+
 		resultRelInfo->ri_PartitionCheckExpr = ExecPrepareCheck(qual, estate);
 		MemoryContextSwitchTo(oldcxt);
 	}
@@ -1729,6 +1802,33 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
 
+	if (resultRelInfo->ri_partitionKeyExprs)
+	{
+		TupleTableSlot *partexprs_slot = resultRelInfo->ri_partitionKeyExprsSlot;
+		Datum	*values;
+		bool	*isnull;
+		ListCell *lc;
+		AttrNumber attrno = 1;
+
+		Assert(partexprs_slot != NULL);
+		ExecClearTuple(partexprs_slot);
+
+		values = partexprs_slot->tts_values;
+		isnull = partexprs_slot->tts_isnull;
+
+		foreach(lc, resultRelInfo->ri_partitionKeyExprs)
+		{
+			ExprState   *partexpr = lfirst(lc);
+
+			values[attrno-1] = ExecEvalExprSwitchContext(partexpr, econtext,
+												  &isnull[attrno-1]);
+			attrno++;
+		}
+		ExecStoreVirtualTuple(partexprs_slot);
+
+		econtext->ecxt_outertuple = partexprs_slot;
+	}
+
 	/*
 	 * As in case of the catalogued constraints, we treat a NULL result as
 	 * success here, not a failure.
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 2348eb3154..eaabdc6ee1 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -287,7 +287,7 @@ ExecFindPartition(ModifyTableState *mtstate,
 	 * routing the tuple if it doesn't belong in the root table itself.
 	 */
 	if (rootResultRelInfo->ri_RelationDesc->rd_rel->relispartition)
-		ExecPartitionCheck(rootResultRelInfo, slot, estate, true);
+		ExecPartitionCheck(rootResultRelInfo, slot, estate, NULL, true);
 
 	/* start with the root partitioned table */
 	dispatch = pd[0];
@@ -298,6 +298,8 @@ ExecFindPartition(ModifyTableState *mtstate,
 
 		CHECK_FOR_INTERRUPTS();
 
+		rel = dispatch->reldesc;
+
 		/*
 		 * Check if the saved partition accepts this tuple by evaluating its
 		 * partition constraint against the tuple.  If it does, we save a trip
@@ -317,7 +319,7 @@ ExecFindPartition(ModifyTableState *mtstate,
 												rri->ri_PartitionTupleSlot);
 			else
 				tmpslot = rootslot;
-			if (ExecPartitionCheck(rri, tmpslot, estate, false))
+			if (ExecPartitionCheck(rri, tmpslot, estate, rel, false))
 			{
 				/* and restore ecxt's scantuple */
 				ecxt->ecxt_scantuple = ecxt_scantuple_saved;
@@ -327,7 +329,6 @@ ExecFindPartition(ModifyTableState *mtstate,
 			dispatch->lastPartInfo = rri = NULL;
 		}
 
-		rel = dispatch->reldesc;
 		partdesc = dispatch->partdesc;
 
 		/*
@@ -513,7 +514,7 @@ ExecFindPartition(ModifyTableState *mtstate,
 					slot = rootslot;
 			}
 
-			ExecPartitionCheck(rri, slot, estate, true);
+			ExecPartitionCheck(rri, slot, estate, NULL, true);
 		}
 	}
 
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 1e285e0349..9f37f55090 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -437,7 +437,7 @@ ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo,
 		if (rel->rd_att->constr)
 			ExecConstraints(resultRelInfo, slot, estate);
 		if (rel->rd_rel->relispartition)
-			ExecPartitionCheck(resultRelInfo, slot, estate, true);
+			ExecPartitionCheck(resultRelInfo, slot, estate, NULL, true);
 
 		/* OK, store the tuple and create index entries for it */
 		simple_table_tuple_insert(resultRelInfo->ri_RelationDesc, slot);
@@ -505,7 +505,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_att->constr)
 			ExecConstraints(resultRelInfo, slot, estate);
 		if (rel->rd_rel->relispartition)
-			ExecPartitionCheck(resultRelInfo, slot, estate, true);
+			ExecPartitionCheck(resultRelInfo, slot, estate, NULL, true);
 
 		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
 								  &update_indexes);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 379b056310..c46fff0f7e 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -791,7 +791,7 @@ ExecInsert(ModifyTableState *mtstate,
 			(resultRelInfo->ri_RootResultRelInfo == NULL ||
 			 (resultRelInfo->ri_TrigDesc &&
 			  resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
-			ExecPartitionCheck(resultRelInfo, slot, estate, true);
+			ExecPartitionCheck(resultRelInfo, slot, estate, NULL, true);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
 		{
@@ -1710,7 +1710,7 @@ lreplace:;
 		 */
 		partition_constraint_failed =
 			resultRelationDesc->rd_rel->relispartition &&
-			!ExecPartitionCheck(resultRelInfo, slot, estate, false);
+			!ExecPartitionCheck(resultRelInfo, slot, estate, NULL, false);
 
 		if (!partition_constraint_failed &&
 			resultRelInfo->ri_WithCheckOptions != NIL)
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 6ba447ea97..ee4f7bf06f 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1747,7 +1747,7 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				 */
 				if (!partrel->rd_rel->relispartition ||
 					ExecPartitionCheck(partrelinfo, remoteslot_part, estate,
-									   false))
+									   NULL, false))
 				{
 					/*
 					 * Yes, so simply UPDATE the partition.  We don't call
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3dc03c913e..c44ac1d7f3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -207,7 +207,8 @@ extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern void ExecConstraints(ResultRelInfo *resultRelInfo,
 							TupleTableSlot *slot, EState *estate);
 extern bool ExecPartitionCheck(ResultRelInfo *resultRelInfo,
-							   TupleTableSlot *slot, EState *estate, bool emitError);
+							   TupleTableSlot *slot, EState *estate,
+							   Relation parentrel, bool emitError);
 extern void ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 										TupleTableSlot *slot, EState *estate);
 extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 7795a69490..9984171576 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -496,6 +496,13 @@ typedef struct ResultRelInfo
 	/* partition check expression state (NULL if not set up yet) */
 	ExprState  *ri_PartitionCheckExpr;
 
+	/*
+	 * Information used by ExecPartitionCheck() to optimize some cases where
+	 * the parent's partition key contains arbitrary expressions.
+	 */
+	List	   *ri_partitionKeyExprs;
+	TupleTableSlot *ri_partitionKeyExprsSlot;
+
 	/*
 	 * Information needed by tuple routing target relations
 	 *
