diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 28b98d10ae..59289f8d4d 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -89,6 +89,8 @@ 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, @@ -2672,7 +2674,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ExecCopySlot(newslot, epqslot_clean); } - trigtuple = ExecFetchSlotHeapTuple(oldslot, true, &should_free_trig); + trigtuple = MaterializeTupleForTrigger(oldslot, &should_free_trig); } else { @@ -2925,6 +2927,9 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo) } +/* + * Fetch tuple into "oldslot", dealing with locking and EPQ if necessary + */ static bool GetTupleForTrigger(EState *estate, EPQState *epqstate, @@ -3038,6 +3043,40 @@ 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? */ diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index 57efa29020..c19aac9674 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -214,6 +214,38 @@ select * from trigtest; ----+---- (0 rows) +drop table trigtest; +-- Check behavior with an implicit column default, too (bug #16644) +create table trigtest (a integer); +create trigger trigger_return_old + before insert or delete or update on trigtest + for each row execute procedure trigger_return_old(); +insert into trigtest values(1); +select * from trigtest; + a +--- + 1 +(1 row) + +alter table trigtest add column b integer default 42 not null; +select * from trigtest; + a | b +---+---- + 1 | 42 +(1 row) + +update trigtest set a = 2 where a = 1 returning *; + a | b +---+---- + 1 | 42 +(1 row) + +select * from trigtest; + a | b +---+---- + 1 | 42 +(1 row) + drop table trigtest; create sequence ttdummy_seq increment 10 start 0 minvalue 0; create table tttest ( diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index 8f66df9f3b..bf2e73abf6 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -154,6 +154,24 @@ select * from trigtest; drop table trigtest; +-- Check behavior with an implicit column default, too (bug #16644) +create table trigtest (a integer); + +create trigger trigger_return_old + before insert or delete or update on trigtest + for each row execute procedure trigger_return_old(); + +insert into trigtest values(1); +select * from trigtest; + +alter table trigtest add column b integer default 42 not null; + +select * from trigtest; +update trigtest set a = 2 where a = 1 returning *; +select * from trigtest; + +drop table trigtest; + create sequence ttdummy_seq increment 10 start 0 minvalue 0; create table tttest (