diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 86594bd..004d480 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -24,6 +24,7 @@
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "optimizer/optimizer.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
@@ -683,6 +684,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		ExprContext *econtext;
 		List	   *returningList;
 		int			firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
+		int			col;
+		Bitmapset  *returning_attrs = NULL;
+		bool		returning_has_syscol = false;
 
 		/* See the comment above for WCO lists. */
 		Assert((node->operation == CMD_INSERT &&
@@ -730,6 +734,29 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		leaf_part_rri->ri_projectReturning =
 			ExecBuildProjectionInfo(returningList, econtext, slot,
 									&mtstate->ps, RelationGetDescr(partrel));
+
+		/*
+		 * If RETURNING will access partition's system columns, which the root
+		 * partitioned table's virtual tuple slot will not be able to provide,
+		 * create a partition-specific slot that will into which the tuple
+		 * being inserted will be copied.
+		 */
+		pull_varattnos((Node *) returningList, firstVarno, &returning_attrs);
+		col = -1;
+		while ((col = bms_next_member(returning_attrs, col)) >= 0)
+		{
+			AttrNumber  attno = col + FirstLowInvalidHeapAttributeNumber;
+
+			if (attno < 0)
+			{
+				returning_has_syscol = true;
+				break;
+			}
+		}
+
+		if (returning_has_syscol)
+			leaf_part_rri->ri_PartitionTupleSlot =
+				table_slot_create(partrel, &estate->es_tupleTable);
 	}
 
 	/* Set up information needed for routing tuples to the partition. */
@@ -969,9 +996,11 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
 	 * If a partition has a different rowtype than the root parent, initialize
 	 * a slot dedicated to storing this partition's tuples.  The slot is used
 	 * for various operations that are applied to tuples after routing, such
-	 * as checking constraints.
+	 * as checking constraints.  Caller may have already initialized the slot
+	 * for RETURNING's perusal.
 	 */
-	if (partRelInfo->ri_RootToPartitionMap != NULL)
+	if (partRelInfo->ri_RootToPartitionMap != NULL &&
+		partRelInfo->ri_PartitionTupleSlot == NULL)
 	{
 		Relation	partrel = partRelInfo->ri_RelationDesc;
 
@@ -983,8 +1012,6 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
 		partRelInfo->ri_PartitionTupleSlot =
 			table_slot_create(partrel, &estate->es_tupleTable);
 	}
-	else
-		partRelInfo->ri_PartitionTupleSlot = NULL;
 
 	/*
 	 * If the partition is a foreign table, let the FDW init itself for
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29e07b7..8fac474 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1904,7 +1904,8 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
 	}
 
 	/*
-	 * Convert the tuple, if necessary.
+	 * Convert the tuple, if necessary, and store in the partition-specific
+	 * slot, if any.
 	 */
 	map = partrel->ri_RootToPartitionMap;
 	if (map != NULL)
@@ -1913,6 +1914,12 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
 
 		slot = execute_attr_map_slot(map->attrMap, slot, new_slot);
 	}
+	else if (partrel->ri_PartitionTupleSlot)
+	{
+		TupleTableSlot *new_slot = partrel->ri_PartitionTupleSlot;
+
+		slot = ExecCopySlot(new_slot, slot);
+	}
 
 	*partRelInfo = partrel;
 	return slot;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index da50ee3..02403a8 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -960,13 +960,13 @@ select tableoid::regclass, * from mcrparted order by a, b;
 (11 rows)
 
 drop table mcrparted;
--- check that wholerow vars in the RETURNING list work with partitioned tables
+-- check that wholerow and system columns in the RETURNING list work with partitioned tables
 create table returningwrtest (a int) partition by list (a);
 create table returningwrtest1 partition of returningwrtest for values in (1);
-insert into returningwrtest values (1) returning returningwrtest;
- returningwrtest 
------------------
- (1)
+insert into returningwrtest values (1) returning returningwrtest, ctid, xmin = pg_current_xact_id()::xid AS xmin_correct;
+ returningwrtest | ctid  | xmin_correct 
+-----------------+-------+--------------
+ (1)             | (0,1) | t
 (1 row)
 
 -- check also that the wholerow vars in RETURNING list are converted as needed
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 963faa1..c40a4b9 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -590,10 +590,10 @@ insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10),
 select tableoid::regclass, * from mcrparted order by a, b;
 drop table mcrparted;
 
--- check that wholerow vars in the RETURNING list work with partitioned tables
+-- check that wholerow and system columns in the RETURNING list work with partitioned tables
 create table returningwrtest (a int) partition by list (a);
 create table returningwrtest1 partition of returningwrtest for values in (1);
-insert into returningwrtest values (1) returning returningwrtest;
+insert into returningwrtest values (1) returning returningwrtest, ctid, xmin = pg_current_xact_id()::xid AS xmin_correct;
 
 -- check also that the wholerow vars in RETURNING list are converted as needed
 alter table returningwrtest add b text;
