Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit7fee787

Browse files
committed
Fix some more cases of missed GENERATED-column updates.
If UPDATE is forced to retry after an EvalPlanQual check, it neglectedto repeat GENERATED-column computations, even though those might wellhave changed since we're dealing with a different tuple than before.Fixing this is mostly a matter of looping back a bit further whenwe retry. In v15 and HEAD that's most easily done by altering the APIof ExecUpdateAct so that it includes computing GENERATED expressions.Also, if an UPDATE in a partitioned table turns into a cross-partitionINSERT operation, we failed to recompute GENERATED columns. That's abug since8bf6ec3 allowed partitions to have different generationexpressions; although it seems to have no ill effects before that.Fixing this is messier because we can now have situations where the samequery needs both the UPDATE-aligned set of GENERATED columns and theINSERT-aligned set, and it's unclear which set will be generated first(else we could hack things by forcing the INSERT-aligned set to begenerated, which is indeed howfe9e658 made it work for MERGE).The best fix seems to be to build and store separate sets of expressionsfor the INSERT and UPDATE cases. That would create ABI issues in theback branches, but so far it seems we can leave this alone in the backbranches.Per bug #17823 from Hisahiro Kauchi. The first part of this affects allbranches back to v12 where GENERATED columns were added.Discussion:https://postgr.es/m/17823-b64909cf7d63de84@postgresql.org
1 parentd937904 commit7fee787

File tree

8 files changed

+280
-207
lines changed

8 files changed

+280
-207
lines changed

‎src/backend/executor/execMain.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1232,7 +1232,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
12321232
resultRelInfo->ri_FdwState=NULL;
12331233
resultRelInfo->ri_usesFdwDirectModify= false;
12341234
resultRelInfo->ri_ConstraintExprs=NULL;
1235-
resultRelInfo->ri_GeneratedExprs=NULL;
1235+
resultRelInfo->ri_GeneratedExprsI=NULL;
1236+
resultRelInfo->ri_GeneratedExprsU=NULL;
12361237
resultRelInfo->ri_projectReturning=NULL;
12371238
resultRelInfo->ri_onConflictArbiterIndexes=NIL;
12381239
resultRelInfo->ri_onConflict=NULL;

‎src/backend/executor/execUtils.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,8 +1339,8 @@ ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
13391339
Bitmapset*
13401340
ExecGetExtraUpdatedCols(ResultRelInfo*relinfo,EState*estate)
13411341
{
1342-
/*In some code paths wecan reach here before initializing the info */
1343-
if (relinfo->ri_GeneratedExprs==NULL)
1342+
/*Compute the info if wedidn't already */
1343+
if (relinfo->ri_GeneratedExprsU==NULL)
13441344
ExecInitStoredGenerated(relinfo,estate,CMD_UPDATE);
13451345
returnrelinfo->ri_extraUpdatedCols;
13461346
}

‎src/backend/executor/nodeModifyTable.c

Lines changed: 75 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
363368
void
364369
ExecInitStoredGenerated(ResultRelInfo*resultRelInfo,
@@ -368,12 +373,11 @@ ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
368373
Relationrel=resultRelInfo->ri_RelationDesc;
369374
TupleDesctupdesc=RelationGetDescr(rel);
370375
intnatts=tupdesc->natts;
376+
ExprState**ri_GeneratedExprs;
377+
intri_NumGeneratedNeeded;
371378
Bitmapset*updatedCols;
372379
MemoryContextoldContext;
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 (inti=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
TupleDesctupdesc=RelationGetDescr(rel);
453475
intnatts=tupdesc->natts;
454476
ExprContext*econtext=GetPerTupleExprContext(estate);
477+
ExprState**ri_GeneratedExprs;
455478
MemoryContextoldContext;
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_attributeattr=TupleDescAttr(tupdesc,i);
489517

490-
if (resultRelInfo->ri_GeneratedExprs[i])
518+
if (ri_GeneratedExprs[i])
491519
{
492520
Datumval;
493521
boolisnull;
@@ -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 forExecUpdate
1941+
* ExecUpdatePrepareSlot -- subroutine forExecUpdateAct
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
*/
19171946
staticvoid
19181947
ExecUpdatePrepareSlot(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
*/
19711999
lreplace:
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
elseif (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+
*/
22962331
redo_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
/*

‎src/include/nodes/execnodes.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ typedef struct ResultRelInfo
462462
*/
463463
AttrNumberri_RowIdAttNo;
464464

465-
/* ForINSERT/UPDATE, attnums of generated columns to be computed */
465+
/* For UPDATE, attnums of generated columns to be computed */
466466
Bitmapset*ri_extraUpdatedCols;
467467

468468
/* Projection to generate new tuple in an INSERT/UPDATE */
@@ -516,11 +516,13 @@ typedef struct ResultRelInfo
516516
/* array of constraint-checking expr states */
517517
ExprState**ri_ConstraintExprs;
518518

519-
/* array of stored generated columns expr states */
520-
ExprState**ri_GeneratedExprs;
519+
/* arrays of stored generated columns expr states, for INSERT and UPDATE */
520+
ExprState**ri_GeneratedExprsI;
521+
ExprState**ri_GeneratedExprsU;
521522

522523
/* number of stored generated columns we need to compute */
523-
intri_NumGeneratedNeeded;
524+
intri_NumGeneratedNeededI;
525+
intri_NumGeneratedNeededU;
524526

525527
/* list of RETURNING expressions */
526528
List*ri_returningList;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp