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 */