Skip to content

Commit

Permalink
In INSERT/UPDATE, use the table's real tuple descriptor as target.
Browse files Browse the repository at this point in the history
Previously, ExecInitModifyTable relied on ExecInitJunkFilter,
and thence ExecCleanTypeFromTL, to build the target descriptor from
the query tlist.  While we just checked (in ExecCheckPlanOutput)
that the tlist produces compatible output, this is not a great
substitute for the relation's actual tuple descriptor that's
available from the relcache.  For one thing, dropped columns will
not be correctly marked attisdropped; it's a bit surprising that
we've gotten away with that this long.  But the real reason for
being concerned with this is that using the table's descriptor means
that the slot will have correct attrmissing data, allowing us to
revert the klugy fix of commit ba9f18a.  (This commit undoes
that one's changes in trigger.c, but keeps the new test case.)
Thus we can solve the bogus-trigger-tuple problem with fewer cycles
rather than more.

No back-patch, since this doesn't fix any additional bug, and it
seems somewhat more likely to have unforeseen side effects than
ba9f18a's narrow fix.

Discussion: https://postgr.es/m/16644-5da7ef98a7ac4545@postgresql.org
  • Loading branch information
tglsfdc committed Oct 26, 2020
1 parent fa42c2e commit 20d3fe9
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 50 deletions.
38 changes: 1 addition & 37 deletions src/backend/commands/trigger.c
Expand Up @@ -89,8 +89,6 @@ static bool GetTupleForTrigger(EState *estate,
LockTupleMode lockmode,
TupleTableSlot *oldslot,
TupleTableSlot **newSlot);
static HeapTuple MaterializeTupleForTrigger(TupleTableSlot *slot,
bool *shouldFree);
static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
Trigger *trigger, TriggerEvent event,
Bitmapset *modifiedCols,
Expand Down Expand Up @@ -2674,7 +2672,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
ExecCopySlot(newslot, epqslot_clean);
}

trigtuple = MaterializeTupleForTrigger(oldslot, &should_free_trig);
trigtuple = ExecFetchSlotHeapTuple(oldslot, true, &should_free_trig);
}
else
{
Expand Down Expand Up @@ -3043,40 +3041,6 @@ GetTupleForTrigger(EState *estate,
return true;
}

/*
* Extract a HeapTuple that we can pass off to trigger functions.
*
* We must materialize the tuple and make sure it is not dependent on any
* attrmissing data. This is needed for the old row in BEFORE UPDATE
* triggers, since they can choose to pass back this exact tuple as the update
* result, causing the tuple to be inserted into an executor slot that lacks
* the attrmissing data.
*
* Currently we don't seem to need to remove the attrmissing dependency in any
* other cases, but keep this as a separate function to simplify fixing things
* if that changes.
*/
static HeapTuple
MaterializeTupleForTrigger(TupleTableSlot *slot, bool *shouldFree)
{
HeapTuple tup;
TupleDesc tupdesc = slot->tts_tupleDescriptor;

tup = ExecFetchSlotHeapTuple(slot, true, shouldFree);
if (HeapTupleHeaderGetNatts(tup->t_data) < tupdesc->natts &&
tupdesc->constr && tupdesc->constr->missing)
{
HeapTuple newtup;

newtup = heap_expand_tuple(tup, tupdesc);
if (*shouldFree)
heap_freetuple(tup);
*shouldFree = true;
tup = newtup;
}
return tup;
}

/*
* Is trigger enabled to fire?
*/
Expand Down
42 changes: 34 additions & 8 deletions src/backend/executor/execJunk.c
Expand Up @@ -54,23 +54,48 @@
*
* The source targetlist is passed in. The output tuple descriptor is
* built from the non-junk tlist entries.
* An optional resultSlot can be passed as well.
* An optional resultSlot can be passed as well; otherwise, we create one.
*/
JunkFilter *
ExecInitJunkFilter(List *targetList, TupleTableSlot *slot)
{
JunkFilter *junkfilter;
TupleDesc cleanTupType;
int cleanLength;
AttrNumber *cleanMap;
ListCell *t;
AttrNumber cleanResno;

/*
* Compute the tuple descriptor for the cleaned tuple.
*/
cleanTupType = ExecCleanTypeFromTL(targetList);

/*
* The rest is the same as ExecInitJunkFilterInsertion, ie, we want to map
* every non-junk targetlist column into the output tuple.
*/
return ExecInitJunkFilterInsertion(targetList, cleanTupType, slot);
}

/*
* ExecInitJunkFilterInsertion
*
* Initialize a JunkFilter for insertions into a table.
*
* Here, we are given the target "clean" tuple descriptor rather than
* inferring it from the targetlist. Although the target descriptor can
* contain deleted columns, that is not of concern here, since the targetlist
* should contain corresponding NULL constants (cf. ExecCheckPlanOutput).
* It is assumed that the caller has checked that the table's columns match up
* with the non-junk columns of the targetlist.
*/
JunkFilter *
ExecInitJunkFilterInsertion(List *targetList,
TupleDesc cleanTupType,
TupleTableSlot *slot)
{
JunkFilter *junkfilter;
int cleanLength;
AttrNumber *cleanMap;
ListCell *t;
AttrNumber cleanResno;

/*
* Use the given slot, or make a new slot if we weren't given one.
*/
Expand All @@ -93,17 +118,18 @@ ExecInitJunkFilter(List *targetList, TupleTableSlot *slot)
if (cleanLength > 0)
{
cleanMap = (AttrNumber *) palloc(cleanLength * sizeof(AttrNumber));
cleanResno = 1;
cleanResno = 0;
foreach(t, targetList)
{
TargetEntry *tle = lfirst(t);

if (!tle->resjunk)
{
cleanMap[cleanResno - 1] = tle->resno;
cleanMap[cleanResno] = tle->resno;
cleanResno++;
}
}
Assert(cleanResno == cleanLength);
}
else
cleanMap = NULL;
Expand Down
22 changes: 17 additions & 5 deletions src/backend/executor/nodeModifyTable.c
Expand Up @@ -2591,15 +2591,27 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
TupleTableSlot *junkresslot;

subplan = mtstate->mt_plans[i]->plan;
if (operation == CMD_INSERT || operation == CMD_UPDATE)
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
subplan->targetlist);

junkresslot =
ExecInitExtraTupleSlot(estate, NULL,
table_slot_callbacks(resultRelInfo->ri_RelationDesc));
j = ExecInitJunkFilter(subplan->targetlist,
junkresslot);

/*
* For an INSERT or UPDATE, the result tuple must always match
* the target table's descriptor. For a DELETE, it won't
* (indeed, there's probably no non-junk output columns).
*/
if (operation == CMD_INSERT || operation == CMD_UPDATE)
{
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
subplan->targetlist);
j = ExecInitJunkFilterInsertion(subplan->targetlist,
RelationGetDescr(resultRelInfo->ri_RelationDesc),
junkresslot);
}
else
j = ExecInitJunkFilter(subplan->targetlist,
junkresslot);

if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
Expand Down
3 changes: 3 additions & 0 deletions src/include/executor/executor.h
Expand Up @@ -156,6 +156,9 @@ extern void ResetTupleHashTable(TupleHashTable hashtable);
*/
extern JunkFilter *ExecInitJunkFilter(List *targetList,
TupleTableSlot *slot);
extern JunkFilter *ExecInitJunkFilterInsertion(List *targetList,
TupleDesc cleanTupType,
TupleTableSlot *slot);
extern JunkFilter *ExecInitJunkFilterConversion(List *targetList,
TupleDesc cleanTupType,
TupleTableSlot *slot);
Expand Down

0 comments on commit 20d3fe9

Please sign in to comment.