@@ -356,9 +356,14 @@ ExecCheckTIDVisible(EState *estate,
356356/*
357357 * Initialize to compute stored generated columns for a tuple
358358 *
359- * This fills the resultRelInfo's ri_GeneratedExprs and ri_extraUpdatedCols
360- * fields. (Currently, ri_extraUpdatedCols is consulted only in UPDATE,
361- * but we might as well fill it for INSERT too.)
359+ * This fills the resultRelInfo's ri_GeneratedExprsI/ri_NumGeneratedNeededI
360+ * or ri_GeneratedExprsU/ri_NumGeneratedNeededU fields, depending on cmdtype.
361+ * If cmdType == CMD_UPDATE, the ri_extraUpdatedCols field is filled too.
362+ *
363+ * Note: usually, a given query would need only one of ri_GeneratedExprsI and
364+ * ri_GeneratedExprsU per result rel; but MERGE can need both, and so can
365+ * cross-partition UPDATEs, since a partition might be the target of both
366+ * UPDATE and INSERT actions.
362367 */
363368void
364369ExecInitStoredGenerated (ResultRelInfo * resultRelInfo ,
@@ -368,12 +373,11 @@ ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
368373 Relation rel = resultRelInfo -> ri_RelationDesc ;
369374 TupleDesc tupdesc = RelationGetDescr (rel );
370375 int natts = tupdesc -> natts ;
376+ ExprState * * ri_GeneratedExprs ;
377+ int ri_NumGeneratedNeeded ;
371378 Bitmapset * updatedCols ;
372379 MemoryContext oldContext ;
373380
374- /* Don't call twice */
375- Assert (resultRelInfo -> ri_GeneratedExprs == NULL );
376-
377381 /* Nothing to do if no generated columns */
378382 if (!(tupdesc -> constr && tupdesc -> constr -> has_generated_stored ))
379383 return ;
@@ -396,9 +400,8 @@ ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
396400 */
397401 oldContext = MemoryContextSwitchTo (estate -> es_query_cxt );
398402
399- resultRelInfo -> ri_GeneratedExprs =
400- (ExprState * * ) palloc0 (natts * sizeof (ExprState * ));
401- resultRelInfo -> ri_NumGeneratedNeeded = 0 ;
403+ ri_GeneratedExprs = (ExprState * * ) palloc0 (natts * sizeof (ExprState * ));
404+ ri_NumGeneratedNeeded = 0 ;
402405
403406 for (int i = 0 ; i < natts ; i ++ )
404407 {
@@ -427,16 +430,35 @@ ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
427430 }
428431
429432 /* No luck, so prepare the expression for execution */
430- resultRelInfo -> ri_GeneratedExprs [i ] = ExecPrepareExpr (expr , estate );
431- resultRelInfo -> ri_NumGeneratedNeeded ++ ;
432-
433- /* And mark this column in resultRelInfo->ri_extraUpdatedCols */
434- resultRelInfo -> ri_extraUpdatedCols =
435- bms_add_member (resultRelInfo -> ri_extraUpdatedCols ,
436- i + 1 - FirstLowInvalidHeapAttributeNumber );
433+ ri_GeneratedExprs [i ] = ExecPrepareExpr (expr , estate );
434+ ri_NumGeneratedNeeded ++ ;
435+
436+ /* If UPDATE, mark column in resultRelInfo->ri_extraUpdatedCols */
437+ if (cmdtype == CMD_UPDATE )
438+ resultRelInfo -> ri_extraUpdatedCols =
439+ bms_add_member (resultRelInfo -> ri_extraUpdatedCols ,
440+ i + 1 - FirstLowInvalidHeapAttributeNumber );
437441 }
438442 }
439443
444+ /* Save in appropriate set of fields */
445+ if (cmdtype == CMD_UPDATE )
446+ {
447+ /* Don't call twice */
448+ Assert (resultRelInfo -> ri_GeneratedExprsU == NULL );
449+
450+ resultRelInfo -> ri_GeneratedExprsU = ri_GeneratedExprs ;
451+ resultRelInfo -> ri_NumGeneratedNeededU = ri_NumGeneratedNeeded ;
452+ }
453+ else
454+ {
455+ /* Don't call twice */
456+ Assert (resultRelInfo -> ri_GeneratedExprsI == NULL );
457+
458+ resultRelInfo -> ri_GeneratedExprsI = ri_GeneratedExprs ;
459+ resultRelInfo -> ri_NumGeneratedNeededI = ri_NumGeneratedNeeded ;
460+ }
461+
440462 MemoryContextSwitchTo (oldContext );
441463}
442464
@@ -452,6 +474,7 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
452474 TupleDesc tupdesc = RelationGetDescr (rel );
453475 int natts = tupdesc -> natts ;
454476 ExprContext * econtext = GetPerTupleExprContext (estate );
477+ ExprState * * ri_GeneratedExprs ;
455478 MemoryContext oldContext ;
456479 Datum * values ;
457480 bool * nulls ;
@@ -460,20 +483,25 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
460483 Assert (tupdesc -> constr && tupdesc -> constr -> has_generated_stored );
461484
462485 /*
463- * For relations named directly in the query, ExecInitStoredGenerated
464- * should have been called already; but this might not have happened yet
465- * for a partition child rel. Also, it's convenient for outside callers
466- * to not have to call ExecInitStoredGenerated explicitly.
467- */
468- if (resultRelInfo -> ri_GeneratedExprs == NULL )
469- ExecInitStoredGenerated (resultRelInfo , estate , cmdtype );
470-
471- /*
472- * If no generated columns have been affected by this change, then skip
473- * the rest.
486+ * Initialize the expressions if we didn't already, and check whether we
487+ * can exit early because nothing needs to be computed.
474488 */
475- if (resultRelInfo -> ri_NumGeneratedNeeded == 0 )
476- return ;
489+ if (cmdtype == CMD_UPDATE )
490+ {
491+ if (resultRelInfo -> ri_GeneratedExprsU == NULL )
492+ ExecInitStoredGenerated (resultRelInfo , estate , cmdtype );
493+ if (resultRelInfo -> ri_NumGeneratedNeededU == 0 )
494+ return ;
495+ ri_GeneratedExprs = resultRelInfo -> ri_GeneratedExprsU ;
496+ }
497+ else
498+ {
499+ if (resultRelInfo -> ri_GeneratedExprsI == NULL )
500+ ExecInitStoredGenerated (resultRelInfo , estate , cmdtype );
501+ /* Early exit is impossible given the prior Assert */
502+ Assert (resultRelInfo -> ri_NumGeneratedNeededI > 0 );
503+ ri_GeneratedExprs = resultRelInfo -> ri_GeneratedExprsI ;
504+ }
477505
478506 oldContext = MemoryContextSwitchTo (GetPerTupleMemoryContext (estate ));
479507
@@ -487,7 +515,7 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
487515 {
488516 Form_pg_attribute attr = TupleDescAttr (tupdesc , i );
489517
490- if (resultRelInfo -> ri_GeneratedExprs [i ])
518+ if (ri_GeneratedExprs [i ])
491519 {
492520 Datum val ;
493521 bool isnull ;
@@ -496,7 +524,7 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
496524
497525 econtext -> ecxt_scantuple = slot ;
498526
499- val = ExecEvalExpr (resultRelInfo -> ri_GeneratedExprs [i ], econtext , & isnull );
527+ val = ExecEvalExpr (ri_GeneratedExprs [i ], econtext , & isnull );
500528
501529 /*
502530 * We must make a copy of val as we have no guarantees about where
@@ -1910,9 +1938,10 @@ ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
19101938}
19111939
19121940/*
1913- * ExecUpdatePrepareSlot -- subroutine for ExecUpdate
1941+ * ExecUpdatePrepareSlot -- subroutine for ExecUpdateAct
19141942 *
19151943 * Apply the final modifications to the tuple slot before the update.
1944+ * (This is split out because we also need it in the foreign-table code path.)
19161945 */
19171946static void
19181947ExecUpdatePrepareSlot (ResultRelInfo * resultRelInfo ,
@@ -1962,13 +1991,14 @@ ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
19621991 updateCxt -> crossPartUpdate = false;
19631992
19641993 /*
1965- * If we generate a new candidate tuple after EvalPlanQual testing, we
1966- * must loop back here and recheck any RLS policies and constraints. (We
1967- * don't need to redo triggers, however. If there are any BEFORE triggers
1968- * then trigger.c will have done table_tuple_lock to lock the correct
1969- * tuple, so there's no need to do them again.)
1994+ * If we move the tuple to a new partition, we loop back here to recompute
1995+ * GENERATED values (which are allowed to be different across partitions)
1996+ * and recheck any RLS policies and constraints. We do not fire any
1997+ * BEFORE triggers of the new partition, however.
19701998 */
19711999lreplace :
2000+ /* Fill in GENERATEd columns */
2001+ ExecUpdatePrepareSlot (resultRelInfo , slot , estate );
19722002
19732003 /* ensure slot is independent, consider e.g. EPQ */
19742004 ExecMaterializeSlot (slot );
@@ -2268,6 +2298,7 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
22682298 }
22692299 else if (resultRelInfo -> ri_FdwRoutine )
22702300 {
2301+ /* Fill in GENERATEd columns */
22712302 ExecUpdatePrepareSlot (resultRelInfo , slot , estate );
22722303
22732304 /*
@@ -2290,9 +2321,13 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
22902321 }
22912322 else
22922323 {
2293- /* Fill in the slot appropriately */
2294- ExecUpdatePrepareSlot (resultRelInfo , slot , estate );
2295-
2324+ /*
2325+ * If we generate a new candidate tuple after EvalPlanQual testing, we
2326+ * must loop back here to try again. (We don't need to redo triggers,
2327+ * however. If there are any BEFORE triggers then trigger.c will have
2328+ * done table_tuple_lock to lock the correct tuple, so there's no need
2329+ * to do them again.)
2330+ */
22962331redo_act :
22972332 result = ExecUpdateAct (context , resultRelInfo , tupleid , oldtuple , slot ,
22982333 canSetTag , & updateCxt );
@@ -2876,7 +2911,6 @@ ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
28762911 result = TM_Ok ;
28772912 break ;
28782913 }
2879- ExecUpdatePrepareSlot (resultRelInfo , newslot , context -> estate );
28802914 result = ExecUpdateAct (context , resultRelInfo , tupleid , NULL ,
28812915 newslot , false, & updateCxt );
28822916 if (result == TM_Ok && updateCxt .updated )
@@ -4135,15 +4169,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
41354169 elog (ERROR , "could not find junk wholerow column" );
41364170 }
41374171 }
4138-
4139- /*
4140- * For INSERT/UPDATE/MERGE, prepare to evaluate any generated columns.
4141- * We must do this now, even if we never insert or update any rows,
4142- * because we have to fill resultRelInfo->ri_extraUpdatedCols for
4143- * possible use by the trigger machinery.
4144- */
4145- if (operation == CMD_INSERT || operation == CMD_UPDATE || operation == CMD_MERGE )
4146- ExecInitStoredGenerated (resultRelInfo , estate , operation );
41474172 }
41484173
41494174 /*
0 commit comments