@@ -356,9 +356,14 @@ ExecCheckTIDVisible(EState *estate,
356
356
/*
357
357
* Initialize to compute stored generated columns for a tuple
358
358
*
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.
362
367
*/
363
368
void
364
369
ExecInitStoredGenerated (ResultRelInfo * resultRelInfo ,
@@ -368,12 +373,11 @@ ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
368
373
Relation rel = resultRelInfo -> ri_RelationDesc ;
369
374
TupleDesc tupdesc = RelationGetDescr (rel );
370
375
int natts = tupdesc -> natts ;
376
+ ExprState * * ri_GeneratedExprs ;
377
+ int ri_NumGeneratedNeeded ;
371
378
Bitmapset * updatedCols ;
372
379
MemoryContext oldContext ;
373
380
374
- /* Don't call twice */
375
- Assert (resultRelInfo -> ri_GeneratedExprs == NULL );
376
-
377
381
/* Nothing to do if no generated columns */
378
382
if (!(tupdesc -> constr && tupdesc -> constr -> has_generated_stored ))
379
383
return ;
@@ -396,9 +400,8 @@ ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
396
400
*/
397
401
oldContext = MemoryContextSwitchTo (estate -> es_query_cxt );
398
402
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 ;
402
405
403
406
for (int i = 0 ; i < natts ; i ++ )
404
407
{
@@ -427,16 +430,35 @@ ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
427
430
}
428
431
429
432
/* 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 );
437
441
}
438
442
}
439
443
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
+
440
462
MemoryContextSwitchTo (oldContext );
441
463
}
442
464
@@ -452,6 +474,7 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
452
474
TupleDesc tupdesc = RelationGetDescr (rel );
453
475
int natts = tupdesc -> natts ;
454
476
ExprContext * econtext = GetPerTupleExprContext (estate );
477
+ ExprState * * ri_GeneratedExprs ;
455
478
MemoryContext oldContext ;
456
479
Datum * values ;
457
480
bool * nulls ;
@@ -460,20 +483,25 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
460
483
Assert (tupdesc -> constr && tupdesc -> constr -> has_generated_stored );
461
484
462
485
/*
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.
474
488
*/
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
+ }
477
505
478
506
oldContext = MemoryContextSwitchTo (GetPerTupleMemoryContext (estate ));
479
507
@@ -487,7 +515,7 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
487
515
{
488
516
Form_pg_attribute attr = TupleDescAttr (tupdesc , i );
489
517
490
- if (resultRelInfo -> ri_GeneratedExprs [i ])
518
+ if (ri_GeneratedExprs [i ])
491
519
{
492
520
Datum val ;
493
521
bool isnull ;
@@ -496,7 +524,7 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
496
524
497
525
econtext -> ecxt_scantuple = slot ;
498
526
499
- val = ExecEvalExpr (resultRelInfo -> ri_GeneratedExprs [i ], econtext , & isnull );
527
+ val = ExecEvalExpr (ri_GeneratedExprs [i ], econtext , & isnull );
500
528
501
529
/*
502
530
* We must make a copy of val as we have no guarantees about where
@@ -1910,9 +1938,10 @@ ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
1910
1938
}
1911
1939
1912
1940
/*
1913
- * ExecUpdatePrepareSlot -- subroutine for ExecUpdate
1941
+ * ExecUpdatePrepareSlot -- subroutine for ExecUpdateAct
1914
1942
*
1915
1943
* 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.)
1916
1945
*/
1917
1946
static void
1918
1947
ExecUpdatePrepareSlot (ResultRelInfo * resultRelInfo ,
@@ -1962,13 +1991,14 @@ ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
1962
1991
updateCxt -> crossPartUpdate = false;
1963
1992
1964
1993
/*
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.
1970
1998
*/
1971
1999
lreplace :
2000
+ /* Fill in GENERATEd columns */
2001
+ ExecUpdatePrepareSlot (resultRelInfo , slot , estate );
1972
2002
1973
2003
/* ensure slot is independent, consider e.g. EPQ */
1974
2004
ExecMaterializeSlot (slot );
@@ -2268,6 +2298,7 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
2268
2298
}
2269
2299
else if (resultRelInfo -> ri_FdwRoutine )
2270
2300
{
2301
+ /* Fill in GENERATEd columns */
2271
2302
ExecUpdatePrepareSlot (resultRelInfo , slot , estate );
2272
2303
2273
2304
/*
@@ -2290,9 +2321,13 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
2290
2321
}
2291
2322
else
2292
2323
{
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
+ */
2296
2331
redo_act :
2297
2332
result = ExecUpdateAct (context , resultRelInfo , tupleid , oldtuple , slot ,
2298
2333
canSetTag , & updateCxt );
@@ -2876,7 +2911,6 @@ ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
2876
2911
result = TM_Ok ;
2877
2912
break ;
2878
2913
}
2879
- ExecUpdatePrepareSlot (resultRelInfo , newslot , context -> estate );
2880
2914
result = ExecUpdateAct (context , resultRelInfo , tupleid , NULL ,
2881
2915
newslot , false, & updateCxt );
2882
2916
if (result == TM_Ok && updateCxt .updated )
@@ -4135,15 +4169,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
4135
4169
elog (ERROR , "could not find junk wholerow column" );
4136
4170
}
4137
4171
}
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 );
4147
4172
}
4148
4173
4149
4174
/*
0 commit comments