mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-10-06 21:56:52 +02:00
Fix handling of multiple AFTER ROW triggers on a foreign table.
AfterTriggerExecute() retrieves a fresh tuple or pair of tuples from a
tuplestore and then stores the tuple(s) in the passed-in slot(s) if
AFTER_TRIGGER_FDW_FETCH, while it uses the most-recently-retrieved
tuple(s) stored in the slot(s) if AFTER_TRIGGER_FDW_REUSE. This was
done correctly before 12, but commit ff11e7f4b
broke it by mistakenly
clearing the tuple(s) stored in the slot(s) in that function, leading to
an assertion failure as reported in bug #16139 from Alexander Lakhin.
Also, fix some other issues with the aforementioned commit in passing:
* For tg_newslot, which is a slot added to the TriggerData struct by the
commit to store new updated tuples, it didn't ensure the slot was NULL
if there was no such tuple.
* The commit failed to update the documentation about the trigger
interface.
Author: Etsuro Fujita
Backpatch-through: 12
Discussion: https://postgr.es/m/16139-94f9ccf0db6119ec%40postgresql.org
This commit is contained in:
parent
001362cfdc
commit
547e454cbc
@ -6459,6 +6459,36 @@ DROP TRIGGER trig_row_after ON rem1;
|
|||||||
DROP TRIGGER trig_stmt_before ON rem1;
|
DROP TRIGGER trig_stmt_before ON rem1;
|
||||||
DROP TRIGGER trig_stmt_after ON rem1;
|
DROP TRIGGER trig_stmt_after ON rem1;
|
||||||
DELETE from rem1;
|
DELETE from rem1;
|
||||||
|
-- Test multiple AFTER ROW triggers on a foreign table
|
||||||
|
CREATE TRIGGER trig_row_after1
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON rem1
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||||
|
CREATE TRIGGER trig_row_after2
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON rem1
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||||
|
insert into rem1 values(1,'insert');
|
||||||
|
NOTICE: trig_row_after1(23, skidoo) AFTER ROW INSERT ON rem1
|
||||||
|
NOTICE: NEW: (1,insert)
|
||||||
|
NOTICE: trig_row_after2(23, skidoo) AFTER ROW INSERT ON rem1
|
||||||
|
NOTICE: NEW: (1,insert)
|
||||||
|
update rem1 set f2 = 'update' where f1 = 1;
|
||||||
|
NOTICE: trig_row_after1(23, skidoo) AFTER ROW UPDATE ON rem1
|
||||||
|
NOTICE: OLD: (1,insert),NEW: (1,update)
|
||||||
|
NOTICE: trig_row_after2(23, skidoo) AFTER ROW UPDATE ON rem1
|
||||||
|
NOTICE: OLD: (1,insert),NEW: (1,update)
|
||||||
|
update rem1 set f2 = f2 || f2;
|
||||||
|
NOTICE: trig_row_after1(23, skidoo) AFTER ROW UPDATE ON rem1
|
||||||
|
NOTICE: OLD: (1,update),NEW: (1,updateupdate)
|
||||||
|
NOTICE: trig_row_after2(23, skidoo) AFTER ROW UPDATE ON rem1
|
||||||
|
NOTICE: OLD: (1,update),NEW: (1,updateupdate)
|
||||||
|
delete from rem1;
|
||||||
|
NOTICE: trig_row_after1(23, skidoo) AFTER ROW DELETE ON rem1
|
||||||
|
NOTICE: OLD: (1,updateupdate)
|
||||||
|
NOTICE: trig_row_after2(23, skidoo) AFTER ROW DELETE ON rem1
|
||||||
|
NOTICE: OLD: (1,updateupdate)
|
||||||
|
-- cleanup
|
||||||
|
DROP TRIGGER trig_row_after1 ON rem1;
|
||||||
|
DROP TRIGGER trig_row_after2 ON rem1;
|
||||||
-- Test WHEN conditions
|
-- Test WHEN conditions
|
||||||
CREATE TRIGGER trig_row_before_insupd
|
CREATE TRIGGER trig_row_before_insupd
|
||||||
BEFORE INSERT OR UPDATE ON rem1
|
BEFORE INSERT OR UPDATE ON rem1
|
||||||
@ -6689,7 +6719,7 @@ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1
|
|||||||
NOTICE: NEW: (13,"test triggered !")
|
NOTICE: NEW: (13,"test triggered !")
|
||||||
ctid
|
ctid
|
||||||
--------
|
--------
|
||||||
(0,29)
|
(0,32)
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
-- cleanup
|
-- cleanup
|
||||||
|
@ -1485,6 +1485,23 @@ DROP TRIGGER trig_stmt_after ON rem1;
|
|||||||
|
|
||||||
DELETE from rem1;
|
DELETE from rem1;
|
||||||
|
|
||||||
|
-- Test multiple AFTER ROW triggers on a foreign table
|
||||||
|
CREATE TRIGGER trig_row_after1
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON rem1
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||||
|
|
||||||
|
CREATE TRIGGER trig_row_after2
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON rem1
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||||
|
|
||||||
|
insert into rem1 values(1,'insert');
|
||||||
|
update rem1 set f2 = 'update' where f1 = 1;
|
||||||
|
update rem1 set f2 = f2 || f2;
|
||||||
|
delete from rem1;
|
||||||
|
|
||||||
|
-- cleanup
|
||||||
|
DROP TRIGGER trig_row_after1 ON rem1;
|
||||||
|
DROP TRIGGER trig_row_after2 ON rem1;
|
||||||
|
|
||||||
-- Test WHEN conditions
|
-- Test WHEN conditions
|
||||||
|
|
||||||
|
@ -511,8 +511,8 @@ typedef struct TriggerData
|
|||||||
HeapTuple tg_trigtuple;
|
HeapTuple tg_trigtuple;
|
||||||
HeapTuple tg_newtuple;
|
HeapTuple tg_newtuple;
|
||||||
Trigger *tg_trigger;
|
Trigger *tg_trigger;
|
||||||
Buffer tg_trigtuplebuf;
|
TupleTableSlot *tg_trigslot;
|
||||||
Buffer tg_newtuplebuf;
|
TupleTableSlot *tg_newslot;
|
||||||
Tuplestorestate *tg_oldtable;
|
Tuplestorestate *tg_oldtable;
|
||||||
Tuplestorestate *tg_newtable;
|
Tuplestorestate *tg_newtable;
|
||||||
} TriggerData;
|
} TriggerData;
|
||||||
@ -714,21 +714,21 @@ typedef struct Trigger
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><structfield>tg_trigtuplebuf</structfield></term>
|
<term><structfield>tg_trigslot</structfield></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The buffer containing <structfield>tg_trigtuple</structfield>, or <symbol>InvalidBuffer</symbol> if there
|
The slot containing <structfield>tg_trigtuple</structfield>,
|
||||||
is no such tuple or it is not stored in a disk buffer.
|
or a <symbol>NULL</symbol> pointer if there is no such tuple.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><structfield>tg_newtuplebuf</structfield></term>
|
<term><structfield>tg_newslot</structfield></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The buffer containing <structfield>tg_newtuple</structfield>, or <symbol>InvalidBuffer</symbol> if there
|
The slot containing <structfield>tg_newtuple</structfield>,
|
||||||
is no such tuple or it is not stored in a disk buffer.
|
or a <symbol>NULL</symbol> pointer if there is no such tuple.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -4258,12 +4258,17 @@ AfterTriggerExecute(EState *estate,
|
|||||||
LocTriggerData.tg_trigtuple =
|
LocTriggerData.tg_trigtuple =
|
||||||
ExecFetchSlotHeapTuple(trig_tuple_slot1, true, &should_free_trig);
|
ExecFetchSlotHeapTuple(trig_tuple_slot1, true, &should_free_trig);
|
||||||
|
|
||||||
|
if ((evtshared->ats_event & TRIGGER_EVENT_OPMASK) ==
|
||||||
|
TRIGGER_EVENT_UPDATE)
|
||||||
|
{
|
||||||
LocTriggerData.tg_newslot = trig_tuple_slot2;
|
LocTriggerData.tg_newslot = trig_tuple_slot2;
|
||||||
LocTriggerData.tg_newtuple =
|
LocTriggerData.tg_newtuple =
|
||||||
((evtshared->ats_event & TRIGGER_EVENT_OPMASK) ==
|
ExecFetchSlotHeapTuple(trig_tuple_slot2, true, &should_free_new);
|
||||||
TRIGGER_EVENT_UPDATE) ?
|
}
|
||||||
ExecFetchSlotHeapTuple(trig_tuple_slot2, true, &should_free_new) : NULL;
|
else
|
||||||
|
{
|
||||||
|
LocTriggerData.tg_newtuple = NULL;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -4358,10 +4363,14 @@ AfterTriggerExecute(EState *estate,
|
|||||||
if (should_free_new)
|
if (should_free_new)
|
||||||
heap_freetuple(LocTriggerData.tg_newtuple);
|
heap_freetuple(LocTriggerData.tg_newtuple);
|
||||||
|
|
||||||
|
/* don't clear slots' contents if foreign table */
|
||||||
|
if (trig_tuple_slot1 == NULL)
|
||||||
|
{
|
||||||
if (LocTriggerData.tg_trigslot)
|
if (LocTriggerData.tg_trigslot)
|
||||||
ExecClearTuple(LocTriggerData.tg_trigslot);
|
ExecClearTuple(LocTriggerData.tg_trigslot);
|
||||||
if (LocTriggerData.tg_newslot)
|
if (LocTriggerData.tg_newslot)
|
||||||
ExecClearTuple(LocTriggerData.tg_newslot);
|
ExecClearTuple(LocTriggerData.tg_newslot);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If doing EXPLAIN ANALYZE, stop charging time to this trigger, and count
|
* If doing EXPLAIN ANALYZE, stop charging time to this trigger, and count
|
||||||
@ -4515,13 +4524,14 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
|
|||||||
trigdesc = rInfo->ri_TrigDesc;
|
trigdesc = rInfo->ri_TrigDesc;
|
||||||
finfo = rInfo->ri_TrigFunctions;
|
finfo = rInfo->ri_TrigFunctions;
|
||||||
instr = rInfo->ri_TrigInstrument;
|
instr = rInfo->ri_TrigInstrument;
|
||||||
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
|
|
||||||
{
|
|
||||||
if (slot1 != NULL)
|
if (slot1 != NULL)
|
||||||
{
|
{
|
||||||
ExecDropSingleTupleTableSlot(slot1);
|
ExecDropSingleTupleTableSlot(slot1);
|
||||||
ExecDropSingleTupleTableSlot(slot2);
|
ExecDropSingleTupleTableSlot(slot2);
|
||||||
|
slot1 = slot2 = NULL;
|
||||||
}
|
}
|
||||||
|
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
|
||||||
|
{
|
||||||
slot1 = MakeSingleTupleTableSlot(rel->rd_att,
|
slot1 = MakeSingleTupleTableSlot(rel->rd_att,
|
||||||
&TTSOpsMinimalTuple);
|
&TTSOpsMinimalTuple);
|
||||||
slot2 = MakeSingleTupleTableSlot(rel->rd_att,
|
slot2 = MakeSingleTupleTableSlot(rel->rd_att,
|
||||||
|
Loading…
Reference in New Issue
Block a user