On 2018-Jun-18, Robert Treat wrote:

> One of the things I was thinking about while reading this thread is
> that the scenario of creating "duplicate" triggers on a table meaning
> two triggers doing the same thing is entirely possible now but we
> don't really do anything to prevent it, which is Ok. I've never seen
> much merit in the argument "people should test" (it assumes a certain
> infallibility that just isn't true) but we've also generally been
> pretty good about exposing what is going on so people can debug this
> type of unexpected behavior.
> 
> So +1 for thinking we do need to worry about it. I'm not exactly sure
> how we want to expose that info; with \d+ we list various "Partition
> X:" sections, perhaps adding one for "Partition triggers:" would be
> enough, although I am inclined to think it needs exposure at the \d
> level. One other thing to consider is firing order of said triggers...
> if all parent level triggers fire before child level triggers then the
> above separation is straightforward, but if the execution order is, as
> I suspect, mixed, then it becomes more complicated.

The firing order is alphabetical across the whole set, i.e. parent/child
triggers are interleaved.  See the regression test output at the bottom
of this email.

I looked into adding a "Partition triggers" section because it seemed a
nice idea, but looking at the code I realized it's a bit tough because
we already split triggers in "categories": normal triggers, disabled
triggers, ALWAYS triggers and REPLICA triggers (src/bin/psql/describe.c
line 2795).  Since the trigger being in a partition is orthogonal to
that classification, we would end up with eight groups.  Not sure that's
great.

The attached patch (catversion bump not included -- beware of server
crash if you run it without initdb'ing) keeps the categories the same.
So with my previous example setup, you can see the duplicate triggers in
psql:

alvherre=# \d child 
               Table "public.child"
 Column │  Type   │ Collation │ Nullable │ Default 
────────┼─────────┼───────────┼──────────┼─────────
 a      │ integer │           │          │ 
Partition of: parent FOR VALUES FROM (0) TO (100)
Triggers:
    trig_c AFTER INSERT ON child FOR EACH ROW EXECUTE PROCEDURE noise()
    trig_p AFTER INSERT ON child FOR EACH ROW EXECUTE PROCEDURE noise()

and as soon as you try to drop the second one, you'll learn the truth
about it:

alvherre=# drop trigger trig_p on child;
ERROR:  cannot drop trigger trig_p on table child because trigger trig_p on 
table parent requires it
SUGERENCIA:  You can drop trigger trig_p on table parent instead.
Time: 1,443 ms

I say this is sufficient.


-- Verify that triggers fire in alphabetical order
create table parted_trig (a int) partition by range (a);
create table parted_trig_1 partition of parted_trig for values from (0) to 
(1000)
   partition by range (a);
create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to 
(100);
create table parted_trig_2 partition of parted_trig for values from (1000) to 
(2000);
create trigger zzz after insert on parted_trig for each row execute procedure 
trigger_notice();
create trigger mmm after insert on parted_trig_1_1 for each row execute 
procedure trigger_notice();
create trigger aaa after insert on parted_trig_1 for each row execute procedure 
trigger_notice();
create trigger bbb after insert on parted_trig for each row execute procedure 
trigger_notice();
create trigger qqq after insert on parted_trig_1_1 for each row execute 
procedure trigger_notice();
insert into parted_trig values (50), (1500);
NOTICE:  trigger aaa on parted_trig_1_1 AFTER INSERT for ROW
NOTICE:  trigger bbb on parted_trig_1_1 AFTER INSERT for ROW
NOTICE:  trigger mmm on parted_trig_1_1 AFTER INSERT for ROW
NOTICE:  trigger qqq on parted_trig_1_1 AFTER INSERT for ROW
NOTICE:  trigger zzz on parted_trig_1_1 AFTER INSERT for ROW
NOTICE:  trigger bbb on parted_trig_2 AFTER INSERT for ROW
NOTICE:  trigger zzz on parted_trig_2 AFTER INSERT for ROW

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 57519fe8d6..9542856d6a 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -815,11 +815,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char 
*queryString,
 
        /*
         * Build the new pg_trigger tuple.
-        *
-        * When we're creating a trigger in a partition, we mark it as internal,
-        * even though we don't do the isInternal magic in this function.  This
-        * makes the triggers in partitions identical to the ones in the
-        * partitioned tables, except that they are marked internal.
         */
        memset(nulls, false, sizeof(nulls));
 
@@ -829,7 +824,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
        values[Anum_pg_trigger_tgfoid - 1] = ObjectIdGetDatum(funcoid);
        values[Anum_pg_trigger_tgtype - 1] = Int16GetDatum(tgtype);
        values[Anum_pg_trigger_tgenabled - 1] = 
CharGetDatum(TRIGGER_FIRES_ON_ORIGIN);
-       values[Anum_pg_trigger_tgisinternal - 1] = BoolGetDatum(isInternal || 
in_partition);
+       values[Anum_pg_trigger_tgisinternal - 1] = BoolGetDatum(isInternal);
+       values[Anum_pg_trigger_tginpartition - 1] = BoolGetDatum(in_partition);
        values[Anum_pg_trigger_tgconstrrelid - 1] = 
ObjectIdGetDatum(constrrelid);
        values[Anum_pg_trigger_tgconstrindid - 1] = ObjectIdGetDatum(indexOid);
        values[Anum_pg_trigger_tgconstraint - 1] = 
ObjectIdGetDatum(constraintOid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 463639208d..04850bd8b4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -7444,7 +7444,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int 
numTables)
                                          tbinfo->dobj.name);
 
                resetPQExpBuffer(query);
-               if (fout->remoteVersion >= 90000)
+               if (fout->remoteVersion >= 110000)
                {
                        /*
                         * NB: think not to use pretty=true in 
pg_get_triggerdef.  It
@@ -7458,6 +7458,18 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int 
numTables)
                                                          "tgenabled, tableoid, 
oid "
                                                          "FROM 
pg_catalog.pg_trigger t "
                                                          "WHERE tgrelid = 
'%u'::pg_catalog.oid "
+                                                         "AND NOT tgisinternal 
AND NOT tginpartition",
+                                                         
tbinfo->dobj.catId.oid);
+               }
+               else if (fout->remoteVersion >= 90000)
+               {
+                       appendPQExpBuffer(query,
+                                                         "SELECT tgname, "
+                                                         
"tgfoid::pg_catalog.regproc AS tgfname, "
+                                                         
"pg_catalog.pg_get_triggerdef(oid, false) AS tgdef, "
+                                                         "tgenabled, tableoid, 
oid "
+                                                         "FROM 
pg_catalog.pg_trigger t "
+                                                         "WHERE tgrelid = 
'%u'::pg_catalog.oid "
                                                          "AND NOT 
tgisinternal",
                                                          
tbinfo->dobj.catId.oid);
                }
diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h
index 951d7d86e7..02b8718a8f 100644
--- a/src/include/catalog/pg_trigger.h
+++ b/src/include/catalog/pg_trigger.h
@@ -41,6 +41,7 @@ CATALOG(pg_trigger,2620,TriggerRelationId)
        char            tgenabled;              /* trigger's firing 
configuration WRT
                                                                 * 
session_replication_role */
        bool            tgisinternal;   /* trigger is system-generated */
+       bool            tginpartition;  /* trigger is a clone in a partition */
        Oid                     tgconstrrelid;  /* constraint's FROM table, if 
any */
        Oid                     tgconstrindid;  /* constraint's supporting 
index, if any */
        Oid                     tgconstraint;   /* associated pg_constraint 
entry, if any */

Reply via email to