@@ -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 );
@@ -368,13 +374,25 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype
368
374
* For INSERT, we have to insert the tuple into the target relation
369
375
* and insert appropriate tuples into the index relations.
370
376
*
377
+ * slot contains the new tuple value to be stored.
378
+ * planSlot is the output of the ModifyTable's subplan; we use it
379
+ * to access "junk" columns that are not going to be stored.
380
+ * In a cross-partition UPDATE, srcSlot is the slot that held the
381
+ * updated tuple for the source relation; otherwise it's NULL.
382
+ *
383
+ * returningRelInfo is the resultRelInfo for the source relation of a
384
+ * cross-partition UPDATE; otherwise it's the current result relation.
385
+ * We use it to process RETURNING lists, for reasons explained below.
386
+ *
371
387
* Returns RETURNING result if any, otherwise NULL.
372
388
* ----------------------------------------------------------------
373
389
*/
374
390
static TupleTableSlot *
375
391
ExecInsert (ModifyTableState * mtstate ,
376
392
TupleTableSlot * slot ,
377
393
TupleTableSlot * planSlot ,
394
+ TupleTableSlot * srcSlot ,
395
+ ResultRelInfo * returningRelInfo ,
378
396
EState * estate ,
379
397
bool canSetTag )
380
398
{
@@ -677,8 +695,45 @@ ExecInsert(ModifyTableState *mtstate,
677
695
ExecWithCheckOptions (WCO_VIEW_CHECK , resultRelInfo , slot , estate );
678
696
679
697
/* Process RETURNING if present */
680
- if (resultRelInfo -> ri_projectReturning )
681
- result = ExecProcessReturning (resultRelInfo , slot , planSlot );
698
+ if (returningRelInfo -> ri_projectReturning )
699
+ {
700
+ /*
701
+ * In a cross-partition UPDATE with RETURNING, we have to use the
702
+ * source partition's RETURNING list, because that matches the output
703
+ * of the planSlot, while the destination partition might have
704
+ * different resjunk columns. This means we have to map the
705
+ * destination tuple back to the source's format so we can apply that
706
+ * RETURNING list. This is expensive, but it should be an uncommon
707
+ * corner case, so we won't spend much effort on making it fast.
708
+ *
709
+ * We assume that we can use srcSlot to hold the re-converted tuple.
710
+ * Note that in the common case where the child partitions both match
711
+ * the root's format, previous optimizations will have resulted in
712
+ * slot and srcSlot being identical, cueing us that there's nothing to
713
+ * do here.
714
+ */
715
+ if (returningRelInfo != resultRelInfo && slot != srcSlot )
716
+ {
717
+ Relation srcRelationDesc = returningRelInfo -> ri_RelationDesc ;
718
+ AttrMap * map ;
719
+
720
+ map = build_attrmap_by_name_if_req (RelationGetDescr (resultRelationDesc ),
721
+ RelationGetDescr (srcRelationDesc ));
722
+ if (map )
723
+ {
724
+ TupleTableSlot * origSlot = slot ;
725
+
726
+ slot = execute_attr_map_slot (map , slot , srcSlot );
727
+ slot -> tts_tid = origSlot -> tts_tid ;
728
+ slot -> tts_tableOid = origSlot -> tts_tableOid ;
729
+ free_attrmap (map );
730
+ }
731
+ }
732
+
733
+ result = ExecProcessReturning (returningRelInfo -> ri_projectReturning ,
734
+ RelationGetRelid (resultRelationDesc ),
735
+ slot , planSlot );
736
+ }
682
737
683
738
return result ;
684
739
}
@@ -1027,7 +1082,9 @@ ldelete:;
1027
1082
}
1028
1083
}
1029
1084
1030
- rslot = ExecProcessReturning (resultRelInfo , slot , planSlot );
1085
+ rslot = ExecProcessReturning (resultRelInfo -> ri_projectReturning ,
1086
+ RelationGetRelid (resultRelationDesc ),
1087
+ slot , planSlot );
1031
1088
1032
1089
/*
1033
1090
* Before releasing the target tuple again, make sure rslot has a
@@ -1203,6 +1260,7 @@ lreplace:;
1203
1260
{
1204
1261
bool tuple_deleted ;
1205
1262
TupleTableSlot * ret_slot ;
1263
+ TupleTableSlot * orig_slot = slot ;
1206
1264
TupleTableSlot * epqslot = NULL ;
1207
1265
PartitionTupleRouting * proute = mtstate -> mt_partition_tuple_routing ;
1208
1266
int map_index ;
@@ -1309,6 +1367,7 @@ lreplace:;
1309
1367
mtstate -> rootResultRelInfo , slot );
1310
1368
1311
1369
ret_slot = ExecInsert (mtstate , slot , planSlot ,
1370
+ orig_slot , resultRelInfo ,
1312
1371
estate , canSetTag );
1313
1372
1314
1373
/* Revert ExecPrepareTupleRouting's node change. */
@@ -1505,7 +1564,9 @@ lreplace:;
1505
1564
1506
1565
/* Process RETURNING if present */
1507
1566
if (resultRelInfo -> ri_projectReturning )
1508
- return ExecProcessReturning (resultRelInfo , slot , planSlot );
1567
+ return ExecProcessReturning (resultRelInfo -> ri_projectReturning ,
1568
+ RelationGetRelid (resultRelationDesc ),
1569
+ slot , planSlot );
1509
1570
1510
1571
return NULL ;
1511
1572
}
@@ -2154,7 +2215,9 @@ ExecModifyTable(PlanState *pstate)
2154
2215
* ExecProcessReturning by IterateDirectModify, so no need to
2155
2216
* provide it here.
2156
2217
*/
2157
- slot = ExecProcessReturning (resultRelInfo , NULL , planSlot );
2218
+ slot = ExecProcessReturning (resultRelInfo -> ri_projectReturning ,
2219
+ RelationGetRelid (resultRelInfo -> ri_RelationDesc ),
2220
+ NULL , planSlot );
2158
2221
2159
2222
estate -> es_result_relation_info = saved_resultRelInfo ;
2160
2223
return slot ;
@@ -2244,6 +2307,7 @@ ExecModifyTable(PlanState *pstate)
2244
2307
slot = ExecPrepareTupleRouting (node , estate , proute ,
2245
2308
resultRelInfo , slot );
2246
2309
slot = ExecInsert (node , slot , planSlot ,
2310
+ NULL , estate -> es_result_relation_info ,
2247
2311
estate , node -> canSetTag );
2248
2312
/* Revert ExecPrepareTupleRouting's state change. */
2249
2313
if (proute )
0 commit comments