Don't throw an error if a queued AFTER trigger no longer exists.
authorTom Lane <[email protected]>
Thu, 20 Jun 2024 18:21:36 +0000 (14:21 -0400)
committerTom Lane <[email protected]>
Thu, 20 Jun 2024 18:21:36 +0000 (14:21 -0400)
afterTriggerInvokeEvents and AfterTriggerExecute have always
treated it as an error if the trigger OID mentioned in a queued
after-trigger event can't be found.  However, that fails to
account for the edge case where the trigger's been dropped in
the current transaction since queueing the event.  There seems
no very good reason to disallow that case, so instead silently
do nothing if the trigger OID can't be found.

This does give up a little bit of bug-detection ability, but I don't
recall that these error messages have ever actually revealed a bug,
so it seems mostly theoretical.  Alternatives such as marking
pending events DONE at the time of dropping a trigger would be
complicated and perhaps introduce bugs of their own.

Per bug #18517 from Alexander Lakhin.  Back-patch to all
supported branches.

Discussion: https://postgr.es/m/18517-af2d19882240902c@postgresql.org

src/backend/commands/trigger.c
src/test/regress/expected/triggers.out
src/test/regress/sql/triggers.sql

index 794bfe1f1496f7da019f1ac836e814114729de22..7f3285b071847c59de93035dc1d6071f9e349215 100644 (file)
@@ -4239,13 +4239,17 @@ AfterTriggerExecute(EState *estate,
    bool        should_free_trig = false;
    bool        should_free_new = false;
 
-   /*
-    * Locate trigger in trigdesc.
-    */
    LocTriggerData.tg_trigger = NULL;
    LocTriggerData.tg_trigslot = NULL;
    LocTriggerData.tg_newslot = NULL;
 
+   /*
+    * Locate trigger in trigdesc.  It might not be present, and in fact the
+    * trigdesc could be NULL, if the trigger was dropped since the event was
+    * queued.  In that case, silently do nothing.
+    */
+   if (trigdesc == NULL)
+       return;
    for (tgindx = 0; tgindx < trigdesc->numtriggers; tgindx++)
    {
        if (trigdesc->triggers[tgindx].tgoid == tgoid)
@@ -4255,7 +4259,7 @@ AfterTriggerExecute(EState *estate,
        }
    }
    if (LocTriggerData.tg_trigger == NULL)
-       elog(ERROR, "could not find trigger %u", tgoid);
+       return;
 
    /*
     * If doing EXPLAIN ANALYZE, start charging time to this trigger. We want
@@ -4575,6 +4579,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
                    rInfo = ExecGetTriggerResultRel(estate, evtshared->ats_relid);
                    rel = rInfo->ri_RelationDesc;
                    trigdesc = rInfo->ri_TrigDesc;
+                   /* caution: trigdesc could be NULL here */
                    finfo = rInfo->ri_TrigFunctions;
                    instr = rInfo->ri_TrigInstrument;
                    if (slot1 != NULL)
@@ -4590,9 +4595,6 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
                        slot2 = MakeSingleTupleTableSlot(rel->rd_att,
                                                         &TTSOpsMinimalTuple);
                    }
-                   if (trigdesc == NULL)   /* should not happen */
-                       elog(ERROR, "relation %u has no triggers",
-                            evtshared->ats_relid);
                }
 
                /*
index 79e5e4b3533629d48ccbf2f0fe1019c5f66e282d..58ee950b57e3f52745dff942c260671aa1eafca8 100644 (file)
@@ -3124,6 +3124,17 @@ select * from trig_table;
 
 drop table refd_table, trig_table;
 --
+-- Test that we can drop a not-yet-fired deferred trigger
+--
+create table refd_table (id int primary key);
+create table trig_table (fk int references refd_table initially deferred);
+begin;
+insert into trig_table values (1);
+drop table refd_table cascade;
+NOTICE:  drop cascades to constraint trig_table_fk_fkey on table trig_table
+commit;
+drop table trig_table;
+--
 -- self-referential FKs are even more fun
 --
 create table self_ref (a int primary key,
index d1d6f9974ca0c53951ed9687e7ba3d3018d4ef0f..f889d25142996322d5cd27d4db7470d342999cc2 100644 (file)
@@ -2303,6 +2303,20 @@ select * from trig_table;
 
 drop table refd_table, trig_table;
 
+--
+-- Test that we can drop a not-yet-fired deferred trigger
+--
+
+create table refd_table (id int primary key);
+create table trig_table (fk int references refd_table initially deferred);
+
+begin;
+insert into trig_table values (1);
+drop table refd_table cascade;
+commit;
+
+drop table trig_table;
+
 --
 -- self-referential FKs are even more fun
 --