@@ -149,34 +149,40 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
149
149
/*
150
150
* ExecProcessReturning --- evaluate a RETURNING list
151
151
*
152
- * resultRelInfo: current result rel
152
+ * projectReturning: the projection to evaluate
153
+ * resultRelOid: result relation's OID
153
154
* tupleSlot: slot holding tuple actually inserted/updated/deleted
154
155
* planSlot: slot holding tuple returned by top subplan node
155
156
*
157
+ * In cross-partition UPDATE cases, projectReturning and planSlot are as
158
+ * for the source partition, and tupleSlot must conform to that. But
159
+ * resultRelOid is for the destination partition.
160
+ *
156
161
* Note: If tupleSlot is NULL, the FDW should have already provided econtext's
157
162
* scan tuple.
158
163
*
159
164
* Returns a slot holding the result tuple
160
165
*/
161
166
static TupleTableSlot *
162
- ExecProcessReturning (ResultRelInfo * resultRelInfo ,
167
+ ExecProcessReturning (ProjectionInfo * projectReturning ,
168
+ Oid resultRelOid ,
163
169
TupleTableSlot * tupleSlot ,
164
170
TupleTableSlot * planSlot )
165
171
{
166
- ProjectionInfo * projectReturning = resultRelInfo -> ri_projectReturning ;
167
172
ExprContext * econtext = projectReturning -> pi_exprContext ;
168
173
169
174
/* Make tuple and any needed join variables available to ExecProject */
170
175
if (tupleSlot )
171
176
econtext -> ecxt_scantuple = tupleSlot ;
177
+ else
178
+ Assert (econtext -> ecxt_scantuple );
172
179
econtext -> ecxt_outertuple = planSlot ;
173
180
174
181
/*
175
- * RETURNING expressions might reference the tableoid column, so
176
- * reinitialize tts_tableOid before evaluating them .
182
+ * RETURNING expressions might reference the tableoid column, so be sure
183
+ * we expose the desired OID, ie that of the real target relation .
177
184
*/
178
- econtext -> ecxt_scantuple -> tts_tableOid =
179
- RelationGetRelid (resultRelInfo -> ri_RelationDesc );
185
+ econtext -> ecxt_scantuple -> tts_tableOid = resultRelOid ;
180
186
181
187
/* Compute the RETURNING expressions */
182
188
return ExecProject (projectReturning );
@@ -343,13 +349,25 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
343
349
* For INSERT, we have to insert the tuple into the target relation
344
350
* and insert appropriate tuples into the index relations.
345
351
*
352
+ * slot contains the new tuple value to be stored.
353
+ * planSlot is the output of the ModifyTable's subplan; we use it
354
+ * to access "junk" columns that are not going to be stored.
355
+ * In a cross-partition UPDATE, srcSlot is the slot that held the
356
+ * updated tuple for the source relation; otherwise it's NULL.
357
+ *
358
+ * returningRelInfo is the resultRelInfo for the source relation of a
359
+ * cross-partition UPDATE; otherwise it's the current result relation.
360
+ * We use it to process RETURNING lists, for reasons explained below.
361
+ *
346
362
* Returns RETURNING result if any, otherwise NULL.
347
363
* ----------------------------------------------------------------
348
364
*/
349
365
static TupleTableSlot *
350
366
ExecInsert (ModifyTableState * mtstate ,
351
367
TupleTableSlot * slot ,
352
368
TupleTableSlot * planSlot ,
369
+ TupleTableSlot * srcSlot ,
370
+ ResultRelInfo * returningRelInfo ,
353
371
EState * estate ,
354
372
bool canSetTag )
355
373
{
@@ -652,8 +670,46 @@ ExecInsert(ModifyTableState *mtstate,
652
670
ExecWithCheckOptions (WCO_VIEW_CHECK , resultRelInfo , slot , estate );
653
671
654
672
/* Process RETURNING if present */
655
- if (resultRelInfo -> ri_projectReturning )
656
- result = ExecProcessReturning (resultRelInfo , slot , planSlot );
673
+ if (returningRelInfo -> ri_projectReturning )
674
+ {
675
+ /*
676
+ * In a cross-partition UPDATE with RETURNING, we have to use the
677
+ * source partition's RETURNING list, because that matches the output
678
+ * of the planSlot, while the destination partition might have
679
+ * different resjunk columns. This means we have to map the
680
+ * destination tuple back to the source's format so we can apply that
681
+ * RETURNING list. This is expensive, but it should be an uncommon
682
+ * corner case, so we won't spend much effort on making it fast.
683
+ *
684
+ * We assume that we can use srcSlot to hold the re-converted tuple.
685
+ * Note that in the common case where the child partitions both match
686
+ * the root's format, previous optimizations will have resulted in
687
+ * slot and srcSlot being identical, cueing us that there's nothing to
688
+ * do here.
689
+ */
690
+ if (returningRelInfo != resultRelInfo && slot != srcSlot )
691
+ {
692
+ Relation srcRelationDesc = returningRelInfo -> ri_RelationDesc ;
693
+ AttrNumber * map ;
694
+
695
+ map = convert_tuples_by_name_map_if_req (RelationGetDescr (resultRelationDesc ),
696
+ RelationGetDescr (srcRelationDesc ),
697
+ gettext_noop ("could not convert row type" ));
698
+ if (map )
699
+ {
700
+ TupleTableSlot * origSlot = slot ;
701
+
702
+ slot = execute_attr_map_slot (map , slot , srcSlot );
703
+ slot -> tts_tid = origSlot -> tts_tid ;
704
+ slot -> tts_tableOid = origSlot -> tts_tableOid ;
705
+ pfree (map );
706
+ }
707
+ }
708
+
709
+ result = ExecProcessReturning (returningRelInfo -> ri_projectReturning ,
710
+ RelationGetRelid (resultRelationDesc ),
711
+ slot , planSlot );
712
+ }
657
713
658
714
return result ;
659
715
}
@@ -1002,7 +1058,9 @@ ldelete:;
1002
1058
}
1003
1059
}
1004
1060
1005
- rslot = ExecProcessReturning (resultRelInfo , slot , planSlot );
1061
+ rslot = ExecProcessReturning (resultRelInfo -> ri_projectReturning ,
1062
+ RelationGetRelid (resultRelationDesc ),
1063
+ slot , planSlot );
1006
1064
1007
1065
/*
1008
1066
* Before releasing the target tuple again, make sure rslot has a
@@ -1178,6 +1236,7 @@ lreplace:;
1178
1236
{
1179
1237
bool tuple_deleted ;
1180
1238
TupleTableSlot * ret_slot ;
1239
+ TupleTableSlot * orig_slot = slot ;
1181
1240
TupleTableSlot * epqslot = NULL ;
1182
1241
PartitionTupleRouting * proute = mtstate -> mt_partition_tuple_routing ;
1183
1242
int map_index ;
@@ -1284,6 +1343,7 @@ lreplace:;
1284
1343
mtstate -> rootResultRelInfo , slot );
1285
1344
1286
1345
ret_slot = ExecInsert (mtstate , slot , planSlot ,
1346
+ orig_slot , resultRelInfo ,
1287
1347
estate , canSetTag );
1288
1348
1289
1349
/* Revert ExecPrepareTupleRouting's node change. */
@@ -1480,7 +1540,9 @@ lreplace:;
1480
1540
1481
1541
/* Process RETURNING if present */
1482
1542
if (resultRelInfo -> ri_projectReturning )
1483
- return ExecProcessReturning (resultRelInfo , slot , planSlot );
1543
+ return ExecProcessReturning (resultRelInfo -> ri_projectReturning ,
1544
+ RelationGetRelid (resultRelationDesc ),
1545
+ slot , planSlot );
1484
1546
1485
1547
return NULL ;
1486
1548
}
@@ -2130,7 +2192,9 @@ ExecModifyTable(PlanState *pstate)
2130
2192
* ExecProcessReturning by IterateDirectModify, so no need to
2131
2193
* provide it here.
2132
2194
*/
2133
- slot = ExecProcessReturning (resultRelInfo , NULL , planSlot );
2195
+ slot = ExecProcessReturning (resultRelInfo -> ri_projectReturning ,
2196
+ RelationGetRelid (resultRelInfo -> ri_RelationDesc ),
2197
+ NULL , planSlot );
2134
2198
2135
2199
estate -> es_result_relation_info = saved_resultRelInfo ;
2136
2200
return slot ;
@@ -2220,6 +2284,7 @@ ExecModifyTable(PlanState *pstate)
2220
2284
slot = ExecPrepareTupleRouting (node , estate , proute ,
2221
2285
resultRelInfo , slot );
2222
2286
slot = ExecInsert (node , slot , planSlot ,
2287
+ NULL , estate -> es_result_relation_info ,
2223
2288
estate , node -> canSetTag );
2224
2289
/* Revert ExecPrepareTupleRouting's state change. */
2225
2290
if (proute )
0 commit comments