From e47bf332d6620d2820e7a93b5475a2a1456bd1c7 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 24 Apr 2026 12:43:50 +0800
Subject: [PATCH v7 1/1] FOR PORTION OF guard against FDW

discussion: https://postgr.es/m/626986.1776785090%40sss.pgh.pa.us
commitfest entry: https://commitfest.postgresql.org/patch/
---
 .../postgres_fdw/expected/postgres_fdw.out    | 19 +++++++++++++++--
 contrib/postgres_fdw/sql/postgres_fdw.sql     | 21 +++++++++++++++++--
 src/backend/commands/copyfrom.c               |  2 +-
 src/backend/executor/execMain.c               |  9 +++++++-
 src/backend/executor/execPartition.c          |  4 ++--
 src/backend/executor/nodeModifyTable.c        |  2 +-
 src/backend/parser/analyze.c                  |  6 ------
 src/include/executor/executor.h               |  2 +-
 8 files changed, 49 insertions(+), 16 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 10e87acabef..a0b796a4a12 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -6334,9 +6334,20 @@ DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass;
 (1 row)
 
 -- Test UPDATE FOR PORTION OF
+CREATE TABLE ftpp (
+	c1 int4range NOT NULL,
+	c2 int NOT NULL,
+	c3 text,
+	c4 daterange NOT NULL
+) PARTITION BY RANGE (c2);
+ALTER TABLE ftpp ATTACH PARTITION ft8 FOR VALUES FROM (1) TO (150);
+UPDATE ftpp FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
+SET c2 = c2 + 1
+WHERE c1 = '[1,2)'; 	-- error
+ERROR:  foreign tables don't support FOR PORTION OF
 UPDATE ft8 FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
 SET c2 = c2 + 1
-WHERE c1 = '[1,2)';
+WHERE c1 = '[1,2)';		-- error
 ERROR:  foreign tables don't support FOR PORTION OF
 SELECT * FROM ft8 WHERE c1 = '[1,2)' ORDER BY c1, c4;
   c1   | c2 |   c3   |           c4            
@@ -6345,8 +6356,12 @@ SELECT * FROM ft8 WHERE c1 = '[1,2)' ORDER BY c1, c4;
 (1 row)
 
 -- Test DELETE FOR PORTION OF
+DELETE FROM ftpp FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
+WHERE c1 = '[2,3)';		-- error
+ERROR:  foreign tables don't support FOR PORTION OF
+ALTER TABLE ftpp DETACH PARTITION ft8;
 DELETE FROM ft8 FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
-WHERE c1 = '[2,3)';
+WHERE c1 = '[2,3)';		-- error
 ERROR:  foreign tables don't support FOR PORTION OF
 SELECT * FROM ft8 WHERE c1 = '[2,3)' ORDER BY c1, c4;
   c1   | c2 |   c3   |           c4            
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 79ad5be8bf9..bf3f28c56a5 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -1577,14 +1577,31 @@ DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass;
 DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass;
 
 -- Test UPDATE FOR PORTION OF
+CREATE TABLE ftpp (
+	c1 int4range NOT NULL,
+	c2 int NOT NULL,
+	c3 text,
+	c4 daterange NOT NULL
+) PARTITION BY RANGE (c2);
+ALTER TABLE ftpp ATTACH PARTITION ft8 FOR VALUES FROM (1) TO (150);
+
+UPDATE ftpp FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
+SET c2 = c2 + 1
+WHERE c1 = '[1,2)'; 	-- error
+
 UPDATE ft8 FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
 SET c2 = c2 + 1
-WHERE c1 = '[1,2)';
+WHERE c1 = '[1,2)';		-- error
 SELECT * FROM ft8 WHERE c1 = '[1,2)' ORDER BY c1, c4;
 
 -- Test DELETE FOR PORTION OF
+DELETE FROM ftpp FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
+WHERE c1 = '[2,3)';		-- error
+
+ALTER TABLE ftpp DETACH PARTITION ft8;
+
 DELETE FROM ft8 FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
-WHERE c1 = '[2,3)';
+WHERE c1 = '[2,3)';		-- error
 SELECT * FROM ft8 WHERE c1 = '[2,3)' ORDER BY c1, c4;
 
 -- Test UPDATE/DELETE with RETURNING on a three-table join
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 64ac3063c61..775b1b5a641 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -921,7 +921,7 @@ CopyFrom(CopyFromState cstate)
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
 	/* Verify the named relation is a valid target for INSERT */
-	CheckValidResultRel(resultRelInfo, CMD_INSERT, ONCONFLICT_NONE, NIL);
+	CheckValidResultRel(resultRelInfo, CMD_INSERT, ONCONFLICT_NONE, NIL, NULL);
 
 	ExecOpenIndices(resultRelInfo, false);
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4b30f768680..06de3a4461c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1063,7 +1063,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
  */
 void
 CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
-					OnConflictAction onConflictAction, List *mergeActions)
+					OnConflictAction onConflictAction, List *mergeActions,
+					ModifyTable *mtnode)
 {
 	Relation	resultRel = resultRelInfo->ri_RelationDesc;
 	FdwRoutine *fdwroutine;
@@ -1126,6 +1127,12 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
 								RelationGetRelationName(resultRel))));
 			break;
 		case RELKIND_FOREIGN_TABLE:
+			/* We don't support FOR PORTION OF FDW queries. */
+			if (mtnode && mtnode->forPortionOf)
+				ereport(ERROR,
+						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						errmsg("foreign tables don't support FOR PORTION OF"));
+
 			/* Okay only if the FDW supports it */
 			fdwroutine = resultRelInfo->ri_FdwRoutine;
 			switch (operation)
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index d96d4f9947b..33ec5bfde4c 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -368,7 +368,7 @@ ExecFindPartition(ModifyTableState *mtstate,
 					/* Verify this ResultRelInfo allows INSERTs */
 					CheckValidResultRel(rri, CMD_INSERT,
 										node ? node->onConflictAction : ONCONFLICT_NONE,
-										NIL);
+										NIL, node);
 
 					/*
 					 * Initialize information needed to insert this and
@@ -594,7 +594,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 	 * required when the operation is CMD_UPDATE.
 	 */
 	CheckValidResultRel(leaf_part_rri, CMD_INSERT,
-						node ? node->onConflictAction : ONCONFLICT_NONE, NIL);
+						node ? node->onConflictAction : ONCONFLICT_NONE, NIL, node);
 
 	/*
 	 * Open partition indices.  The user may have asked to check for conflicts
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 4cb057ca4f9..7ff7a3a17e5 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -5296,7 +5296,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		 * Verify result relation is a valid target for the current operation
 		 */
 		CheckValidResultRel(resultRelInfo, operation, node->onConflictAction,
-							mergeActions);
+							mergeActions, node);
 
 		resultRelInfo++;
 		i++;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index ffcf25a6be7..04bae9939b9 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -1335,12 +1335,6 @@ transformForPortionOfClause(ParseState *pstate,
 	ForPortionOfExpr *result;
 	Var		   *rangeVar;
 
-	/* We don't support FOR PORTION OF FDW queries. */
-	if (targetrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("foreign tables don't support FOR PORTION OF")));
-
 	result = makeNode(ForPortionOfExpr);
 
 	/* Look up the FOR PORTION OF name requested. */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 33bbdbfeffb..c690b230b45 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -249,7 +249,7 @@ extern bool ExecCheckPermissions(List *rangeTable,
 extern bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
 								OnConflictAction onConflictAction,
-								List *mergeActions);
+								List *mergeActions, ModifyTable *mtnode);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
 							  Index resultRelationIndex,
-- 
2.34.1

