From e9695c968d2705b0c8986dfbb3fd2409e22acb63 Mon Sep 17 00:00:00 2001
From: Alexander Pyhalov <a.pyhalov@postgrespro.ru>
Date: Wed, 27 Dec 2023 17:31:23 +0300
Subject: [PATCH 6/6] postgres_fdw: fix partition-wise DML

---
 contrib/postgres_fdw/deparse.c                |  12 +-
 .../postgres_fdw/expected/postgres_fdw.out    | 160 ++++++++++++++++++
 contrib/postgres_fdw/postgres_fdw.c           |  98 ++++++++++-
 contrib/postgres_fdw/sql/postgres_fdw.sql     |  38 +++++
 src/backend/optimizer/util/appendinfo.c       |  32 +++-
 src/include/optimizer/appendinfo.h            |   1 +
 6 files changed, 323 insertions(+), 18 deletions(-)

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index f879ff3100f..d4c657fa4d5 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -2309,7 +2309,7 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
 
 	appendStringInfoString(buf, "UPDATE ");
 	deparseRelation(buf, rel);
-	if (foreignrel->reloptkind == RELOPT_JOINREL)
+	if (IS_JOIN_REL(foreignrel))
 		appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, rtindex);
 	appendStringInfoString(buf, " SET ");
 
@@ -2336,7 +2336,7 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
 
 	reset_transmission_modes(nestlevel);
 
-	if (foreignrel->reloptkind == RELOPT_JOINREL)
+	if (IS_JOIN_REL(foreignrel))
 	{
 		List	   *ignore_conds = NIL;
 
@@ -2352,7 +2352,7 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
 	if (additional_conds != NIL)
 		list_free_deep(additional_conds);
 
-	if (foreignrel->reloptkind == RELOPT_JOINREL)
+	if (IS_JOIN_REL(foreignrel))
 		deparseExplicitTargetList(returningList, true, retrieved_attrs,
 								  &context);
 	else
@@ -2417,10 +2417,10 @@ deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root,
 
 	appendStringInfoString(buf, "DELETE FROM ");
 	deparseRelation(buf, rel);
-	if (foreignrel->reloptkind == RELOPT_JOINREL)
+	if (IS_JOIN_REL(foreignrel))
 		appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, rtindex);
 
-	if (foreignrel->reloptkind == RELOPT_JOINREL)
+	if (IS_JOIN_REL(foreignrel))
 	{
 		List	   *ignore_conds = NIL;
 
@@ -2435,7 +2435,7 @@ deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root,
 	if (additional_conds != NIL)
 		list_free_deep(additional_conds);
 
-	if (foreignrel->reloptkind == RELOPT_JOINREL)
+	if (IS_JOIN_REL(foreignrel))
 		deparseExplicitTargetList(returningList, true, retrieved_attrs,
 								  &context);
 	else
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2eb21505fb0..918fce5e8df 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -10136,6 +10136,166 @@ SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a
 (4 rows)
 
 reset enable_sort;
+-- test partition-wise DML
+EXPLAIN (COSTS OFF, VERBOSE)
+UPDATE fprt1 SET b=fprt1.b+1 FROM fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0;
+                                                                  QUERY PLAN                                                                  
+----------------------------------------------------------------------------------------------------------------------------------------------
+ Update on public.fprt1
+   Foreign Update on public.ftprt1_p1 fprt1_1
+   Foreign Update on public.ftprt1_p2 fprt1_2
+   ->  Append
+         ->  Foreign Update
+               Remote SQL: UPDATE public.fprt1_p1 r3 SET b = (r3.b + 1) FROM public.fprt2_p1 r5 WHERE ((r3.a = r5.b)) AND (((r5.a % 25) = 0))
+         ->  Foreign Update
+               Remote SQL: UPDATE public.fprt1_p2 r4 SET b = (r4.b + 1) FROM public.fprt2_p2 r6 WHERE ((r4.a = r6.b)) AND (((r6.a % 25) = 0))
+(8 rows)
+
+UPDATE fprt1 SET b=fprt1.b+1 FROM fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0;
+EXPLAIN (COSTS OFF, VERBOSE)
+UPDATE fprt1 SET b=(fprt1.a+fprt2.b)/2 FROM fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0;
+                                                                      QUERY PLAN                                                                       
+-------------------------------------------------------------------------------------------------------------------------------------------------------
+ Update on public.fprt1
+   Foreign Update on public.ftprt1_p1 fprt1_1
+   Foreign Update on public.ftprt1_p2 fprt1_2
+   ->  Append
+         ->  Foreign Update
+               Remote SQL: UPDATE public.fprt1_p1 r3 SET b = ((r3.a + r5.b) / 2) FROM public.fprt2_p1 r5 WHERE ((r3.a = r5.b)) AND (((r5.a % 25) = 0))
+         ->  Foreign Update
+               Remote SQL: UPDATE public.fprt1_p2 r4 SET b = ((r4.a + r6.b) / 2) FROM public.fprt2_p2 r6 WHERE ((r4.a = r6.b)) AND (((r6.a % 25) = 0))
+(8 rows)
+
+UPDATE fprt1 SET b=(fprt1.a+fprt2.b)/2 FROM fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0;
+-- returning whole row references
+EXPLAIN (COSTS OFF)
+UPDATE fprt1 SET b=fprt1.b+1 FROM fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0 RETURNING fprt1, fprt2;
+              QUERY PLAN               
+---------------------------------------
+ Update on fprt1
+   Foreign Update on ftprt1_p1 fprt1_1
+   Foreign Update on ftprt1_p2 fprt1_2
+   ->  Append
+         ->  Foreign Update
+         ->  Foreign Update
+(6 rows)
+
+UPDATE fprt1 SET b=fprt1.b+1 FROM fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0 RETURNING fprt1, fprt2;
+     fprt1      |     fprt2      
+----------------+----------------
+ (0,1,0000)     | (0,0,0000)
+ (150,151,0003) | (150,150,0003)
+ (250,251,0005) | (250,250,0005)
+ (400,401,0008) | (400,400,0008)
+(4 rows)
+
+-- tableoids are returned correctly
+EXPLAIN (COSTS OFF)
+UPDATE fprt1 t1 SET b = t1.b + 1 FROM fprt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0  RETURNING t2.tableoid::regclass;
+             QUERY PLAN             
+------------------------------------
+ Update on fprt1 t1
+   Foreign Update on ftprt1_p1 t1_1
+   Foreign Update on ftprt1_p2 t1_2
+   ->  Append
+         ->  Foreign Update
+         ->  Foreign Update
+(6 rows)
+
+UPDATE fprt1 t1 SET b = t1.b + 1 FROM fprt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0  RETURNING t2.tableoid::regclass;
+ tableoid  
+-----------
+ ftprt2_p1
+ ftprt2_p1
+ ftprt2_p2
+ ftprt2_p2
+(4 rows)
+
+-- join of several tables
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE fprt1 t1 SET b = t1.b + 1 FROM fprt2 t2, fprt1 t3 WHERE t1.a = t2.b AND t2.b = t3.a AND t1.a % 25 = 0  RETURNING *;
+                                                                                                                                QUERY PLAN                                                                                                                                 
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Update on public.fprt1 t1
+   Output: t1_1.a, t1_1.b, t1_1.c, t2.a, t2.b, t2.c, t3.a, t3.b, t3.c
+   Foreign Update on public.ftprt1_p1 t1_1
+   Foreign Update on public.ftprt1_p2 t1_2
+   ->  Append
+         ->  Foreign Update
+               Remote SQL: UPDATE public.fprt1_p1 r4 SET b = (r4.b + 1) FROM (public.fprt2_p1 r6 INNER JOIN public.fprt1_p1 r8 ON (TRUE)) WHERE ((r4.a = r8.a)) AND ((r4.a = r6.b)) AND (((r4.a % 25) = 0)) RETURNING r4.a, r4.b, r4.c, r6.a, r6.b, r6.c, r8.a, r8.b, r8.c
+         ->  Foreign Update
+               Remote SQL: UPDATE public.fprt1_p2 r5 SET b = (r5.b + 1) FROM (public.fprt2_p2 r7 INNER JOIN public.fprt1_p2 r9 ON (TRUE)) WHERE ((r5.a = r9.a)) AND ((r5.a = r7.b)) AND (((r5.a % 25) = 0)) RETURNING r5.a, r5.b, r5.c, r7.a, r7.b, r7.c, r9.a, r9.b, r9.c
+(9 rows)
+
+UPDATE fprt1 t1 SET b = t1.b + 1 FROM fprt2 t2, fprt1 t3 WHERE t1.a = t2.b AND t2.b = t3.a AND t1.a % 25 = 0  RETURNING *;
+  a  |  b  |  c   |  a  |  b  |  c   |  a  |  b  |  c   
+-----+-----+------+-----+-----+------+-----+-----+------
+   0 |   3 | 0000 |   0 |   0 | 0000 |   0 |   2 | 0000
+ 150 | 153 | 0003 | 150 | 150 | 0003 | 150 | 152 | 0003
+ 250 | 253 | 0005 | 250 | 250 | 0005 | 250 | 252 | 0005
+ 400 | 403 | 0008 | 400 | 400 | 0008 | 400 | 402 | 0008
+(4 rows)
+
+-- left join
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE fprt1 t1 SET b = t1.b + 1 FROM fprt2 t2 LEFT JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a = t2.b AND t1.a % 25 = 0  RETURNING *;
+                                                                                                                              QUERY PLAN                                                                                                                              
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Update on public.fprt1 t1
+   Output: t1_1.a, t1_1.b, t1_1.c, t2.a, t2.b, t2.c, t3.a, t3.b, t3.c
+   Foreign Update on public.ftprt1_p1 t1_1
+   Foreign Update on public.ftprt1_p2 t1_2
+   ->  Append
+         ->  Foreign Update
+               Remote SQL: UPDATE public.fprt1_p1 r5 SET b = (r5.b + 1) FROM (public.fprt2_p1 r7 LEFT JOIN public.fprt1_p1 r9 ON (((r7.b = r9.a)))) WHERE ((r5.a = r7.b)) AND (((r5.a % 25) = 0)) RETURNING r5.a, r5.b, r5.c, r7.a, r7.b, r7.c, r9.a, r9.b, r9.c
+         ->  Foreign Update
+               Remote SQL: UPDATE public.fprt1_p2 r6 SET b = (r6.b + 1) FROM (public.fprt2_p2 r8 LEFT JOIN public.fprt1_p2 r10 ON (((r8.b = r10.a)))) WHERE ((r6.a = r8.b)) AND (((r6.a % 25) = 0)) RETURNING r6.a, r6.b, r6.c, r8.a, r8.b, r8.c, r10.a, r10.b, r10.c
+(9 rows)
+
+UPDATE fprt1 t1 SET b = t1.b + 1 FROM fprt2 t2 LEFT JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a = t2.b AND t1.a % 25 = 0  RETURNING *;
+  a  |  b  |  c   |  a  |  b  |  c   |  a  |  b  |  c   
+-----+-----+------+-----+-----+------+-----+-----+------
+   0 |   4 | 0000 |   0 |   0 | 0000 |   0 |   3 | 0000
+ 150 | 154 | 0003 | 150 | 150 | 0003 | 150 | 153 | 0003
+ 250 | 254 | 0005 | 250 | 250 | 0005 | 250 | 253 | 0005
+ 400 | 404 | 0008 | 400 | 400 | 0008 | 400 | 403 | 0008
+(4 rows)
+
+-- delete
+EXPLAIN (COSTS OFF)
+DELETE FROM fprt1 USING fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 30 = 29;
+              QUERY PLAN               
+---------------------------------------
+ Delete on fprt1
+   Foreign Delete on ftprt1_p1 fprt1_1
+   Foreign Delete on ftprt1_p2 fprt1_2
+   ->  Append
+         ->  Foreign Delete
+         ->  Foreign Delete
+(6 rows)
+
+DELETE FROM fprt1 USING fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 30 = 29;
+EXPLAIN (COSTS OFF)
+DELETE FROM fprt1 USING fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0 RETURNING *;
+              QUERY PLAN               
+---------------------------------------
+ Delete on fprt1
+   Foreign Delete on ftprt1_p1 fprt1_1
+   Foreign Delete on ftprt1_p2 fprt1_2
+   ->  Append
+         ->  Foreign Delete
+         ->  Foreign Delete
+(6 rows)
+
+DELETE FROM fprt1 USING fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0 RETURNING *;
+  a  |  b  |  c   |  a  |  b  |  c   
+-----+-----+------+-----+-----+------
+   0 |   4 | 0000 |   0 |   0 | 0000
+ 150 | 154 | 0003 | 150 | 150 | 0003
+ 250 | 254 | 0005 | 250 | 250 | 0005
+ 400 | 404 | 0008 | 400 | 400 | 0008
+(4 rows)
+
 RESET enable_partitionwise_join;
 -- ===================================================================
 -- test partitionwise aggregates
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 90bf35fd90c..8caa3abaf48 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -245,6 +245,9 @@ typedef struct PgFdwDirectModifyState
 	AttrNumber	oidAttno;		/* attnum of input oid column */
 	bool		hasSystemCols;	/* are there system columns of resultRel? */
 
+	List	   *tidAttnos;		/* attnos of output tid columns */
+	List	   *tidVarnos;		/* varnos of output tid columns */
+
 	/* working memory context */
 	MemoryContext temp_cxt;		/* context for per-tuple temporary data */
 } PgFdwDirectModifyState;
@@ -2465,6 +2468,7 @@ postgresPlanDirectModify(PlannerInfo *root,
 	List	   *params_list = NIL;
 	List	   *returningList = NIL;
 	List	   *retrieved_attrs = NIL;
+	Relids		child_relids = NULL;
 
 	/*
 	 * Decide whether it is safe to modify a foreign table directly.
@@ -2496,6 +2500,12 @@ postgresPlanDirectModify(PlannerInfo *root,
 		foreignrel = find_join_rel(root, fscan->fs_relids);
 		/* We should have a rel for this foreign join. */
 		Assert(foreignrel);
+
+		/*
+		 * If it's a child joinrel, record relids to translate.
+		 */
+		if (IS_OTHER_REL(foreignrel))
+			child_relids = foreignrel->relids;
 	}
 	else
 		foreignrel = root->simple_rel_array[resultRelation];
@@ -2515,7 +2525,7 @@ postgresPlanDirectModify(PlannerInfo *root,
 		 * The expressions of concern are the first N columns of the processed
 		 * targetlist, where N is the length of the rel's update_colnos.
 		 */
-		get_translated_update_targetlist(root, resultRelation,
+		get_translated_update_targetlist(root, resultRelation, child_relids,
 										 &processed_tlist, &targetAttrs);
 		forboth(lc, processed_tlist, lc2, targetAttrs)
 		{
@@ -2568,8 +2578,17 @@ postgresPlanDirectModify(PlannerInfo *root,
 		 * node below.
 		 */
 		if (fscan->scan.scanrelid == 0)
+		{
+			if (foreignrel->reloptkind == RELOPT_OTHER_JOINREL)
+			{
+				returningList = (List *) adjust_appendrel_attrs_multilevel(root,
+																		   (Node *) returningList,
+																		   foreignrel,
+																		   foreignrel->top_parent);
+			}
 			returningList = build_remote_returning(resultRelation, rel,
 												   returningList);
+		}
 	}
 
 	/*
@@ -3244,7 +3263,7 @@ estimate_path_cost_size(PlannerInfo *root,
 				/* Shouldn't get here unless we have LIMIT */
 				Assert(fpextra->has_limit);
 				Assert(foreignrel->reloptkind == RELOPT_BASEREL ||
-					   foreignrel->reloptkind == RELOPT_JOINREL);
+					   IS_JOIN_REL(foreignrel));
 				startup_cost += foreignrel->reltarget->cost.startup;
 				run_cost += foreignrel->reltarget->cost.per_tuple * rows;
 			}
@@ -4432,7 +4451,7 @@ build_remote_returning(Index rtindex, Relation rel, List *returningList)
 
 	Assert(returningList);
 
-	vars = pull_var_clause((Node *) returningList, PVC_INCLUDE_PLACEHOLDERS);
+	vars = pull_var_clause((Node *) returningList, PVC_INCLUDE_PLACEHOLDERS | PVC_INCLUDE_CONVERTROWTYPES);
 
 	/*
 	 * If there's a whole-row reference to the target relation, then we'll
@@ -4442,6 +4461,9 @@ build_remote_returning(Index rtindex, Relation rel, List *returningList)
 	{
 		Var		   *var = (Var *) lfirst(lc);
 
+		while (IsA(var, ConvertRowtypeExpr))
+			var = (Var *) (((ConvertRowtypeExpr *) var)->arg);
+
 		if (IsA(var, Var) &&
 			var->varno == rtindex &&
 			var->varattno == InvalidAttrNumber)
@@ -4496,6 +4518,29 @@ build_remote_returning(Index rtindex, Relation rel, List *returningList)
 			var->varattno != SelfItemPointerAttributeNumber)
 			continue;			/* don't need it */
 
+		/*
+		 * Also no need in whole-row references to the target relation under
+		 * ConvertRowtypeExpr
+		 */
+		if (IsA(var, ConvertRowtypeExpr))
+		{
+			ConvertRowtypeExpr *cre = (ConvertRowtypeExpr *) var;
+			Var		   *cre_var;
+
+			while (IsA(cre->arg, ConvertRowtypeExpr))
+				cre = (ConvertRowtypeExpr *) cre->arg;
+
+
+			cre_var = (Var *) cre->arg;
+
+			if (IsA(cre_var, Var) &&
+				cre_var->varno == rtindex)
+			{
+				Assert(cre_var->varattno == InvalidAttrNumber);
+				continue;
+			}
+		}
+
 		if (tlist_member((Expr *) var, tlist))
 			continue;			/* already got it */
 
@@ -4701,15 +4746,26 @@ init_returning_filter(PgFdwDirectModifyState *dmstate,
 		palloc0(resultTupType->natts * sizeof(AttrNumber));
 
 	dmstate->ctidAttno = dmstate->oidAttno = 0;
+	dmstate->tidAttnos = dmstate->tidVarnos = NIL;
 
 	i = 1;
 	dmstate->hasSystemCols = false;
 	foreach(lc, fdw_scan_tlist)
 	{
 		TargetEntry *tle = (TargetEntry *) lfirst(lc);
-		Var		   *var = (Var *) tle->expr;
+		Expr	   *expr;
+		Var		   *var;
+
+		expr = tle->expr;
+
+		while (IsA(expr, ConvertRowtypeExpr))
+		{
+			ConvertRowtypeExpr *cre = castNode(ConvertRowtypeExpr, expr);
 
-		Assert(IsA(var, Var));
+			expr = cre->arg;
+		}
+
+		var = castNode(Var, expr);
 
 		/*
 		 * If the Var is a column of the target relation to be retrieved from
@@ -4738,10 +4794,15 @@ init_returning_filter(PgFdwDirectModifyState *dmstate,
 				 * relation either.
 				 */
 				Assert(attrno > 0);
-
 				dmstate->attnoMap[attrno - 1] = i;
 			}
 		}
+		/* Record tableoid attributes and corresponding varnos */
+		if (var->varattno == TableOidAttributeNumber)
+		{
+			dmstate->tidAttnos = lappend_int(dmstate->tidAttnos, i);
+			dmstate->tidVarnos = lappend_int(dmstate->tidVarnos, var->varno);
+		}
 		i++;
 	}
 }
@@ -4762,6 +4823,8 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate,
 	Datum	   *old_values;
 	bool	   *old_isnull;
 	int			i;
+	ListCell   *lc1,
+			   *lc2;
 
 	/*
 	 * Use the return tuple slot as a place to store the result tuple.
@@ -4801,6 +4864,29 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate,
 		}
 	}
 
+	/*
+	 * Set tableoid attributes
+	 */
+	forboth(lc1, dmstate->tidAttnos, lc2, dmstate->tidVarnos)
+	{
+		AttrNumber	attno;
+		Index		varno;
+		RangeTblEntry *rte;
+
+		attno = lfirst_int(lc1);
+		varno = lfirst_int(lc2);
+
+		rte = list_nth(estate->es_range_table, varno - 1);
+		Assert(rte->rtekind == RTE_RELATION);
+		Assert(rte->relkind == RELKIND_FOREIGN_TABLE);
+
+		/* Attributes numbering in init_returning_filter() starts with 1 */
+		Assert(attno >= 1);
+
+		slot->tts_values[attno - 1] = ObjectIdGetDatum(rte->relid);
+		slot->tts_isnull[attno - 1] = false;
+	}
+
 	/*
 	 * Build the virtual tuple.
 	 */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 36ad85d4af8..3c719aaaf8f 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -3156,6 +3156,44 @@ SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a
 SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY t2.a FOR UPDATE OF t1;
 reset enable_sort;
 
+-- test partition-wise DML
+EXPLAIN (COSTS OFF, VERBOSE)
+UPDATE fprt1 SET b=fprt1.b+1 FROM fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0;
+UPDATE fprt1 SET b=fprt1.b+1 FROM fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0;
+
+EXPLAIN (COSTS OFF, VERBOSE)
+UPDATE fprt1 SET b=(fprt1.a+fprt2.b)/2 FROM fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0;
+UPDATE fprt1 SET b=(fprt1.a+fprt2.b)/2 FROM fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0;
+
+-- returning whole row references
+EXPLAIN (COSTS OFF)
+UPDATE fprt1 SET b=fprt1.b+1 FROM fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0 RETURNING fprt1, fprt2;
+UPDATE fprt1 SET b=fprt1.b+1 FROM fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0 RETURNING fprt1, fprt2;
+
+-- tableoids are returned correctly
+EXPLAIN (COSTS OFF)
+UPDATE fprt1 t1 SET b = t1.b + 1 FROM fprt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0  RETURNING t2.tableoid::regclass;
+UPDATE fprt1 t1 SET b = t1.b + 1 FROM fprt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0  RETURNING t2.tableoid::regclass;
+
+-- join of several tables
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE fprt1 t1 SET b = t1.b + 1 FROM fprt2 t2, fprt1 t3 WHERE t1.a = t2.b AND t2.b = t3.a AND t1.a % 25 = 0  RETURNING *;
+UPDATE fprt1 t1 SET b = t1.b + 1 FROM fprt2 t2, fprt1 t3 WHERE t1.a = t2.b AND t2.b = t3.a AND t1.a % 25 = 0  RETURNING *;
+
+-- left join
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE fprt1 t1 SET b = t1.b + 1 FROM fprt2 t2 LEFT JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a = t2.b AND t1.a % 25 = 0  RETURNING *;
+UPDATE fprt1 t1 SET b = t1.b + 1 FROM fprt2 t2 LEFT JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a = t2.b AND t1.a % 25 = 0  RETURNING *;
+
+-- delete
+EXPLAIN (COSTS OFF)
+DELETE FROM fprt1 USING fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 30 = 29;
+DELETE FROM fprt1 USING fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 30 = 29;
+
+EXPLAIN (COSTS OFF)
+DELETE FROM fprt1 USING fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0 RETURNING *;
+DELETE FROM fprt1 USING fprt2 WHERE fprt1.a = fprt2.b AND fprt2.a % 25 = 0 RETURNING *;
+
 RESET enable_partitionwise_join;
 
 
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 49897226371..29be8af6cc8 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -691,7 +691,7 @@ adjust_inherited_attnums_multilevel(PlannerInfo *root, List *attnums,
  * relied on for this purpose.)
  */
 void
-get_translated_update_targetlist(PlannerInfo *root, Index relid,
+get_translated_update_targetlist(PlannerInfo *root, Index relid, Relids child_joinrel_relids,
 								 List **processed_tlist, List **update_colnos)
 {
 	/* This is pretty meaningless for commands other than UPDATE. */
@@ -709,11 +709,31 @@ get_translated_update_targetlist(PlannerInfo *root, Index relid,
 	else
 	{
 		Assert(bms_is_member(relid, root->all_result_relids));
-		*processed_tlist = (List *)
-			adjust_appendrel_attrs_multilevel(root,
-											  (Node *) root->processed_tlist,
-											  find_base_rel(root, relid),
-											  find_base_rel(root, root->parse->resultRelation));
+
+		/*
+		 * UPDATE targetlist corresponds to SET clause and so can contain
+		 * arbitrary expressions, including those which contain references not
+		 * only to result relation, but also to other vars. So in case of join
+		 * we should also translate these vars to their parents.
+		 */
+		if (bms_is_empty(child_joinrel_relids))
+		{
+			*processed_tlist = (List *)
+				adjust_appendrel_attrs_multilevel(root,
+												  (Node *) root->processed_tlist,
+												  find_base_rel(root, relid),
+												  find_base_rel(root, root->parse->resultRelation));
+		}
+		else
+		{
+			RelOptInfo *child_joinrel = find_join_rel(root, child_joinrel_relids);
+
+			*processed_tlist = (List *)
+				adjust_appendrel_attrs_multilevel(root,
+												  (Node *) root->processed_tlist,
+												  child_joinrel,
+												  child_joinrel->top_parent);
+		}
 		if (update_colnos)
 			*update_colnos =
 				adjust_inherited_attnums_multilevel(root, root->update_colnos,
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index cc12c9c743d..4f73bb97a50 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -36,6 +36,7 @@ extern List *adjust_inherited_attnums_multilevel(PlannerInfo *root,
 												 Index child_relid,
 												 Index top_parent_relid);
 extern void get_translated_update_targetlist(PlannerInfo *root, Index relid,
+											 Relids child_joinrel_relids,
 											 List **processed_tlist,
 											 List **update_colnos);
 extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
-- 
2.34.1

