@@ -146,21 +146,26 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
146
146
/*
147
147
* ExecProcessReturning --- evaluate a RETURNING list
148
148
*
149
- * resultRelInfo: current result rel
149
+ * projectReturning: the projection to evaluate
150
+ * resultRelOid: result relation's OID
150
151
* tupleSlot: slot holding tuple actually inserted/updated/deleted
151
152
* planSlot: slot holding tuple returned by top subplan node
152
153
*
154
+ * In cross-partition UPDATE cases, projectReturning and planSlot are as
155
+ * for the source partition, and tupleSlot must conform to that. But
156
+ * resultRelOid is for the destination partition.
157
+ *
153
158
* Note: If tupleSlot is NULL, the FDW should have already provided econtext's
154
159
* scan tuple.
155
160
*
156
161
* Returns a slot holding the result tuple
157
162
*/
158
163
static TupleTableSlot *
159
- ExecProcessReturning (ResultRelInfo * resultRelInfo ,
164
+ ExecProcessReturning (ProjectionInfo * projectReturning ,
165
+ Oid resultRelOid ,
160
166
TupleTableSlot * tupleSlot ,
161
167
TupleTableSlot * planSlot )
162
168
{
163
- ProjectionInfo * projectReturning = resultRelInfo -> ri_projectReturning ;
164
169
ExprContext * econtext = projectReturning -> pi_exprContext ;
165
170
166
171
/*
@@ -177,12 +182,13 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
177
182
HeapTuple tuple ;
178
183
179
184
/*
180
- * RETURNING expressions might reference the tableoid column, so
181
- * initialize t_tableOid before evaluating them.
185
+ * RETURNING expressions might reference the tableoid column, so be
186
+ * sure we expose the desired OID, ie that of the real target
187
+ * relation.
182
188
*/
183
189
Assert (!TupIsNull (econtext -> ecxt_scantuple ));
184
190
tuple = ExecMaterializeSlot (econtext -> ecxt_scantuple );
185
- tuple -> t_tableOid = RelationGetRelid ( resultRelInfo -> ri_RelationDesc ) ;
191
+ tuple -> t_tableOid = resultRelOid ;
186
192
}
187
193
econtext -> ecxt_outertuple = planSlot ;
188
194
@@ -256,13 +262,25 @@ ExecCheckTIDVisible(EState *estate,
256
262
* For INSERT, we have to insert the tuple into the target relation
257
263
* and insert appropriate tuples into the index relations.
258
264
*
265
+ * slot contains the new tuple value to be stored.
266
+ * planSlot is the output of the ModifyTable's subplan; we use it
267
+ * to access "junk" columns that are not going to be stored.
268
+ * In a cross-partition UPDATE, srcSlot is the slot that held the
269
+ * updated tuple for the source relation; otherwise it's NULL.
270
+ *
271
+ * returningRelInfo is the resultRelInfo for the source relation of a
272
+ * cross-partition UPDATE; otherwise it's the current result relation.
273
+ * We use it to process RETURNING lists, for reasons explained below.
274
+ *
259
275
* Returns RETURNING result if any, otherwise NULL.
260
276
* ----------------------------------------------------------------
261
277
*/
262
278
static TupleTableSlot *
263
279
ExecInsert (ModifyTableState * mtstate ,
264
280
TupleTableSlot * slot ,
265
281
TupleTableSlot * planSlot ,
282
+ TupleTableSlot * srcSlot ,
283
+ ResultRelInfo * returningRelInfo ,
266
284
EState * estate ,
267
285
bool canSetTag )
268
286
{
@@ -590,8 +608,66 @@ ExecInsert(ModifyTableState *mtstate,
590
608
ExecWithCheckOptions (WCO_VIEW_CHECK , resultRelInfo , slot , estate );
591
609
592
610
/* Process RETURNING if present */
593
- if (resultRelInfo -> ri_projectReturning )
594
- result = ExecProcessReturning (resultRelInfo , slot , planSlot );
611
+ if (returningRelInfo -> ri_projectReturning )
612
+ {
613
+ /*
614
+ * In a cross-partition UPDATE with RETURNING, we have to use the
615
+ * source partition's RETURNING list, because that matches the output
616
+ * of the planSlot, while the destination partition might have
617
+ * different resjunk columns. This means we have to map the
618
+ * destination tuple back to the source's format so we can apply that
619
+ * RETURNING list. This is expensive, but it should be an uncommon
620
+ * corner case, so we won't spend much effort on making it fast.
621
+ *
622
+ * We assume that we can use srcSlot to hold the re-converted tuple.
623
+ * Note that in the common case where the child partitions both match
624
+ * the root's format, previous optimizations will have resulted in
625
+ * slot and srcSlot being identical, cueing us that there's nothing to
626
+ * do here.
627
+ */
628
+ if (returningRelInfo != resultRelInfo && slot != srcSlot )
629
+ {
630
+ Relation srcRelationDesc = returningRelInfo -> ri_RelationDesc ;
631
+ TupleConversionMap * map ;
632
+
633
+ map = convert_tuples_by_name (RelationGetDescr (resultRelationDesc ),
634
+ RelationGetDescr (srcRelationDesc ),
635
+ gettext_noop ("could not convert row type" ));
636
+ if (map )
637
+ {
638
+ HeapTuple origTuple = ExecMaterializeSlot (slot );
639
+ HeapTuple newTuple ;
640
+
641
+ newTuple = do_convert_tuple (origTuple , map );
642
+
643
+ /* do_convert_tuple doesn't copy system columns, so do that */
644
+ newTuple -> t_self = newTuple -> t_data -> t_ctid =
645
+ origTuple -> t_self ;
646
+ newTuple -> t_tableOid = origTuple -> t_tableOid ;
647
+
648
+ HeapTupleHeaderSetXmin (newTuple -> t_data ,
649
+ HeapTupleHeaderGetRawXmin (origTuple -> t_data ));
650
+ HeapTupleHeaderSetCmin (newTuple -> t_data ,
651
+ HeapTupleHeaderGetRawCommandId (origTuple -> t_data ));
652
+ HeapTupleHeaderSetXmax (newTuple -> t_data ,
653
+ InvalidTransactionId );
654
+
655
+ if (RelationGetDescr (resultRelationDesc )-> tdhasoid )
656
+ {
657
+ Assert (RelationGetDescr (srcRelationDesc )-> tdhasoid );
658
+ HeapTupleSetOid (newTuple , HeapTupleGetOid (origTuple ));
659
+ }
660
+
661
+ slot = ExecStoreTuple (newTuple , srcSlot , InvalidBuffer , true);
662
+
663
+ free_conversion_map (map );
664
+ }
665
+ }
666
+
667
+ result = ExecProcessReturning (returningRelInfo -> ri_projectReturning ,
668
+ RelationGetRelid (resultRelationDesc ),
669
+ slot , planSlot );
670
+ }
595
671
596
672
return result ;
597
673
}
@@ -891,7 +967,9 @@ ldelete:;
891
967
ExecStoreTuple (& deltuple , slot , InvalidBuffer , false);
892
968
}
893
969
894
- rslot = ExecProcessReturning (resultRelInfo , slot , planSlot );
970
+ rslot = ExecProcessReturning (resultRelInfo -> ri_projectReturning ,
971
+ RelationGetRelid (resultRelationDesc ),
972
+ slot , planSlot );
895
973
896
974
/*
897
975
* Before releasing the target tuple again, make sure rslot has a
@@ -1068,6 +1146,7 @@ lreplace:;
1068
1146
{
1069
1147
bool tuple_deleted ;
1070
1148
TupleTableSlot * ret_slot ;
1149
+ TupleTableSlot * orig_slot = slot ;
1071
1150
TupleTableSlot * epqslot = NULL ;
1072
1151
PartitionTupleRouting * proute = mtstate -> mt_partition_tuple_routing ;
1073
1152
int map_index ;
@@ -1175,6 +1254,7 @@ lreplace:;
1175
1254
mtstate -> rootResultRelInfo , slot );
1176
1255
1177
1256
ret_slot = ExecInsert (mtstate , slot , planSlot ,
1257
+ orig_slot , resultRelInfo ,
1178
1258
estate , canSetTag );
1179
1259
1180
1260
/* Revert ExecPrepareTupleRouting's node change. */
@@ -1334,7 +1414,9 @@ lreplace:;
1334
1414
1335
1415
/* Process RETURNING if present */
1336
1416
if (resultRelInfo -> ri_projectReturning )
1337
- return ExecProcessReturning (resultRelInfo , slot , planSlot );
1417
+ return ExecProcessReturning (resultRelInfo -> ri_projectReturning ,
1418
+ RelationGetRelid (resultRelationDesc ),
1419
+ slot , planSlot );
1338
1420
1339
1421
return NULL ;
1340
1422
}
@@ -2067,7 +2149,9 @@ ExecModifyTable(PlanState *pstate)
2067
2149
* ExecProcessReturning by IterateDirectModify, so no need to
2068
2150
* provide it here.
2069
2151
*/
2070
- slot = ExecProcessReturning (resultRelInfo , NULL , planSlot );
2152
+ slot = ExecProcessReturning (resultRelInfo -> ri_projectReturning ,
2153
+ RelationGetRelid (resultRelInfo -> ri_RelationDesc ),
2154
+ NULL , planSlot );
2071
2155
2072
2156
estate -> es_result_relation_info = saved_resultRelInfo ;
2073
2157
return slot ;
@@ -2157,6 +2241,7 @@ ExecModifyTable(PlanState *pstate)
2157
2241
slot = ExecPrepareTupleRouting (node , estate , proute ,
2158
2242
resultRelInfo , slot );
2159
2243
slot = ExecInsert (node , slot , planSlot ,
2244
+ NULL , estate -> es_result_relation_info ,
2160
2245
estate , node -> canSetTag );
2161
2246
/* Revert ExecPrepareTupleRouting's state change. */
2162
2247
if (proute )
0 commit comments