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,
ExecCopySlot(newslot, epqslot_clean);
}
- trigtuple = ExecFetchSlotHeapTuple(oldslot, true, &should_free_trig);
+ trigtuple = MaterializeTupleForTrigger(oldslot, &should_free_trig);
}
else
{
}
+/*
+ * Fetch tuple into "oldslot", dealing with locking and EPQ if necessary
+ */
static bool
GetTupleForTrigger(EState *estate,
EPQState *epqstate,
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?
*/
----+----
(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 (
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 (