diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index a666391..f9da3bd 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1737,7 +1737,7 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
  *
  * Note: This is called *iff* resultRelInfo is the main target table.
  */
-static bool
+bool
 ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 				   EState *estate)
 {
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 95e1589..273120a 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -624,6 +624,7 @@ ExecDelete(ItemPointer tupleid,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
+		   bool   *already_deleted,
 		   bool canSetTag)
 {
 	ResultRelInfo *resultRelInfo;
@@ -632,6 +633,9 @@ ExecDelete(ItemPointer tupleid,
 	HeapUpdateFailureData hufd;
 	TupleTableSlot *slot = NULL;
 
+	if (already_deleted)
+		*already_deleted = false;
+
 	/*
 	 * get information on the (current) result relation
 	 */
@@ -775,6 +779,8 @@ ldelete:;
 					}
 				}
 				/* tuple already deleted; nothing to do */
+				if (already_deleted)
+					*already_deleted = true;
 				return NULL;
 
 			default:
@@ -877,7 +883,8 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(ModifyTableState *mtstate,
+		   ItemPointer tupleid,
 		   HeapTuple oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -986,6 +993,27 @@ lreplace:;
 			ExecWithCheckOptions(WCO_RLS_UPDATE_CHECK,
 								 resultRelInfo, slot, estate);
 
+		if (resultRelInfo->ri_PartitionCheck &&
+			!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			bool	already_deleted;
+
+			ExecDelete(tupleid, oldtuple, planSlot, epqstate, estate,
+					   &already_deleted, canSetTag);
+
+			if (already_deleted)
+				return NULL;
+			else
+			{
+				/*
+				 * Don't update estate.es_processed updated again. ExecDelete()
+				 * has already done it above. So use canSetTag=false.
+				 */
+				return ExecInsert(mtstate, slot, planSlot, NULL,
+								  ONCONFLICT_NONE, estate, false);
+			}
+		}
+
 		/*
 		 * Check the constraints of the tuple.  Note that we pass the same
 		 * slot for the orig_slot argument, because unlike ExecInsert(), no
@@ -1312,7 +1340,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 */
 
 	/* Execute UPDATE with projection */
-	*returning = ExecUpdate(&tuple.t_self, NULL,
+	*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
 							mtstate->mt_conflproj, planSlot,
 							&mtstate->mt_epqstate, mtstate->ps.state,
 							canSetTag);
@@ -1582,12 +1610,12 @@ ExecModifyTable(ModifyTableState *node)
 								  estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
 				slot = ExecDelete(tupleid, oldtuple, planSlot,
-								&node->mt_epqstate, estate, node->canSetTag);
+								&node->mt_epqstate, estate, NULL, node->canSetTag);
 				break;
 			default:
 				elog(ERROR, "unknown operation");
@@ -1727,7 +1755,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	/* Build state for INSERT tuple routing */
 	rel = mtstate->resultRelInfo->ri_RelationDesc;
-	if (operation == CMD_INSERT &&
+	if ((operation == CMD_INSERT || operation == CMD_UPDATE) &&
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		PartitionDispatch *partition_dispatch_info;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 02dbe7b..e9a2e07 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -224,6 +224,9 @@ extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
 				  PartitionDispatch *pd,
 				  TupleTableSlot *slot,
 				  EState *estate);
+extern bool ExecPartitionCheck(ResultRelInfo *resultRelInfo,
+							TupleTableSlot *slot,
+							EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index a1e9255..7f27f51 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -209,13 +209,12 @@ create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to
 create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
 insert into part_a_1_a_10 values ('a', 1);
 insert into part_b_10_b_20 values ('b', 10);
--- fail
+-- fail (row movement happens only within the partition subtree)
 update part_a_1_a_10 set a = 'b' where a = 'a';
 ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
 DETAIL:  Failing row contains (b, 1).
+-- ok (row movement)
 update range_parted set b = b - 1 where b = 10;
-ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
-DETAIL:  Failing row contains (b, 9).
 -- ok
 update range_parted set b = b + 1 where b = 10;
 -- cleanup
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index d7721ed..92603e9 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -119,8 +119,9 @@ create table part_b_10_b_20 partition of range_parted for values from ('b', 10)
 insert into part_a_1_a_10 values ('a', 1);
 insert into part_b_10_b_20 values ('b', 10);
 
--- fail
+-- fail (row movement happens only within the partition subtree)
 update part_a_1_a_10 set a = 'b' where a = 'a';
+-- ok (row movement)
 update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
