Skip to content

Commit 20d3fe9

Browse files
committed
In INSERT/UPDATE, use the table's real tuple descriptor as target.
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
1 parent fa42c2e commit 20d3fe9

File tree

4 files changed

+55
-50
lines changed

4 files changed

+55
-50
lines changed

src/backend/commands/trigger.c

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,6 @@ static bool GetTupleForTrigger(EState *estate,
8989
LockTupleMode lockmode,
9090
TupleTableSlot *oldslot,
9191
TupleTableSlot **newSlot);
92-
static HeapTuple MaterializeTupleForTrigger(TupleTableSlot *slot,
93-
bool *shouldFree);
9492
static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
9593
Trigger *trigger, TriggerEvent event,
9694
Bitmapset *modifiedCols,
@@ -2674,7 +2672,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
26742672
ExecCopySlot(newslot, epqslot_clean);
26752673
}
26762674

2677-
trigtuple = MaterializeTupleForTrigger(oldslot, &should_free_trig);
2675+
trigtuple = ExecFetchSlotHeapTuple(oldslot, true, &should_free_trig);
26782676
}
26792677
else
26802678
{
@@ -3043,40 +3041,6 @@ GetTupleForTrigger(EState *estate,
30433041
return true;
30443042
}
30453043

3046-
/*
3047-
* Extract a HeapTuple that we can pass off to trigger functions.
3048-
*
3049-
* We must materialize the tuple and make sure it is not dependent on any
3050-
* attrmissing data. This is needed for the old row in BEFORE UPDATE
3051-
* triggers, since they can choose to pass back this exact tuple as the update
3052-
* result, causing the tuple to be inserted into an executor slot that lacks
3053-
* the attrmissing data.
3054-
*
3055-
* Currently we don't seem to need to remove the attrmissing dependency in any
3056-
* other cases, but keep this as a separate function to simplify fixing things
3057-
* if that changes.
3058-
*/
3059-
static HeapTuple
3060-
MaterializeTupleForTrigger(TupleTableSlot *slot, bool *shouldFree)
3061-
{
3062-
HeapTuple tup;
3063-
TupleDesc tupdesc = slot->tts_tupleDescriptor;
3064-
3065-
tup = ExecFetchSlotHeapTuple(slot, true, shouldFree);
3066-
if (HeapTupleHeaderGetNatts(tup->t_data) < tupdesc->natts &&
3067-
tupdesc->constr && tupdesc->constr->missing)
3068-
{
3069-
HeapTuple newtup;
3070-
3071-
newtup = heap_expand_tuple(tup, tupdesc);
3072-
if (*shouldFree)
3073-
heap_freetuple(tup);
3074-
*shouldFree = true;
3075-
tup = newtup;
3076-
}
3077-
return tup;
3078-
}
3079-
30803044
/*
30813045
* Is trigger enabled to fire?
30823046
*/

src/backend/executor/execJunk.c

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,48 @@
5454
*
5555
* The source targetlist is passed in. The output tuple descriptor is
5656
* built from the non-junk tlist entries.
57-
* An optional resultSlot can be passed as well.
57+
* An optional resultSlot can be passed as well; otherwise, we create one.
5858
*/
5959
JunkFilter *
6060
ExecInitJunkFilter(List *targetList, TupleTableSlot *slot)
6161
{
62-
JunkFilter *junkfilter;
6362
TupleDesc cleanTupType;
64-
int cleanLength;
65-
AttrNumber *cleanMap;
66-
ListCell *t;
67-
AttrNumber cleanResno;
6863

6964
/*
7065
* Compute the tuple descriptor for the cleaned tuple.
7166
*/
7267
cleanTupType = ExecCleanTypeFromTL(targetList);
7368

69+
/*
70+
* The rest is the same as ExecInitJunkFilterInsertion, ie, we want to map
71+
* every non-junk targetlist column into the output tuple.
72+
*/
73+
return ExecInitJunkFilterInsertion(targetList, cleanTupType, slot);
74+
}
75+
76+
/*
77+
* ExecInitJunkFilterInsertion
78+
*
79+
* Initialize a JunkFilter for insertions into a table.
80+
*
81+
* Here, we are given the target "clean" tuple descriptor rather than
82+
* inferring it from the targetlist. Although the target descriptor can
83+
* contain deleted columns, that is not of concern here, since the targetlist
84+
* should contain corresponding NULL constants (cf. ExecCheckPlanOutput).
85+
* It is assumed that the caller has checked that the table's columns match up
86+
* with the non-junk columns of the targetlist.
87+
*/
88+
JunkFilter *
89+
ExecInitJunkFilterInsertion(List *targetList,
90+
TupleDesc cleanTupType,
91+
TupleTableSlot *slot)
92+
{
93+
JunkFilter *junkfilter;
94+
int cleanLength;
95+
AttrNumber *cleanMap;
96+
ListCell *t;
97+
AttrNumber cleanResno;
98+
7499
/*
75100
* Use the given slot, or make a new slot if we weren't given one.
76101
*/
@@ -93,17 +118,18 @@ ExecInitJunkFilter(List *targetList, TupleTableSlot *slot)
93118
if (cleanLength > 0)
94119
{
95120
cleanMap = (AttrNumber *) palloc(cleanLength * sizeof(AttrNumber));
96-
cleanResno = 1;
121+
cleanResno = 0;
97122
foreach(t, targetList)
98123
{
99124
TargetEntry *tle = lfirst(t);
100125

101126
if (!tle->resjunk)
102127
{
103-
cleanMap[cleanResno - 1] = tle->resno;
128+
cleanMap[cleanResno] = tle->resno;
104129
cleanResno++;
105130
}
106131
}
132+
Assert(cleanResno == cleanLength);
107133
}
108134
else
109135
cleanMap = NULL;

src/backend/executor/nodeModifyTable.c

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2591,15 +2591,27 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
25912591
TupleTableSlot *junkresslot;
25922592

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

25982595
junkresslot =
25992596
ExecInitExtraTupleSlot(estate, NULL,
26002597
table_slot_callbacks(resultRelInfo->ri_RelationDesc));
2601-
j = ExecInitJunkFilter(subplan->targetlist,
2602-
junkresslot);
2598+
2599+
/*
2600+
* For an INSERT or UPDATE, the result tuple must always match
2601+
* the target table's descriptor. For a DELETE, it won't
2602+
* (indeed, there's probably no non-junk output columns).
2603+
*/
2604+
if (operation == CMD_INSERT || operation == CMD_UPDATE)
2605+
{
2606+
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
2607+
subplan->targetlist);
2608+
j = ExecInitJunkFilterInsertion(subplan->targetlist,
2609+
RelationGetDescr(resultRelInfo->ri_RelationDesc),
2610+
junkresslot);
2611+
}
2612+
else
2613+
j = ExecInitJunkFilter(subplan->targetlist,
2614+
junkresslot);
26032615

26042616
if (operation == CMD_UPDATE || operation == CMD_DELETE)
26052617
{

src/include/executor/executor.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ extern void ResetTupleHashTable(TupleHashTable hashtable);
156156
*/
157157
extern JunkFilter *ExecInitJunkFilter(List *targetList,
158158
TupleTableSlot *slot);
159+
extern JunkFilter *ExecInitJunkFilterInsertion(List *targetList,
160+
TupleDesc cleanTupType,
161+
TupleTableSlot *slot);
159162
extern JunkFilter *ExecInitJunkFilterConversion(List *targetList,
160163
TupleDesc cleanTupType,
161164
TupleTableSlot *slot);

0 commit comments

Comments
 (0)