Skip to content

Commit 80ba4bb

Browse files
alvherreTel44
andcommitted
Make ALTER TRIGGER RENAME consistent for partitioned tables
Renaming triggers on partitioned tables had two problems: first, it did not recurse to renaming the triggers on the partitions; and second, it failed to prohibit renaming clone triggers. Having triggers with different names in partitions is pointless, and furthermore pg_dump would not preserve names for partitions anyway. Not backpatched -- making the ALTER TRIGGER throw an error in stable versions might cause problems for existing scripts. Co-authored-by: Arne Roland <A.Roland@index.de> Co-authored-by: Álvaro Herrera <alvherre@alvh.no-ip.org> Reviewed-by: Zhihong Yu <zyu@yugabyte.com> Discussion: https://postgr.es/m/d0fd7040c2fb4de1a111b9d9ccc456b8@index.de
1 parent 73c5d2b commit 80ba4bb

File tree

4 files changed

+307
-45
lines changed

4 files changed

+307
-45
lines changed

doc/src/sgml/ref/alter_trigger.sgml

+13-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,20 @@ ALTER TRIGGER <replaceable class="parameter">name</replaceable> ON <replaceable
3131

3232
<para>
3333
<command>ALTER TRIGGER</command> changes properties of an existing
34-
trigger. The <literal>RENAME</literal> clause changes the name of
34+
trigger.
35+
</para>
36+
37+
<para>
38+
The <literal>RENAME</literal> clause changes the name of
3539
the given trigger without otherwise changing the trigger
36-
definition. The <literal>DEPENDS ON EXTENSION</literal> clause marks
40+
definition.
41+
If the table that the trigger is on is a partitioned table,
42+
then corresponding clone triggers in the partitions are
43+
renamed too.
44+
</para>
45+
46+
<para>
47+
The <literal>DEPENDS ON EXTENSION</literal> clause marks
3748
the trigger as dependent on an extension, such that if the extension is
3849
dropped, the trigger will automatically be dropped as well.
3950
</para>

src/backend/commands/trigger.c

+171-43
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ int SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN;
7171
static int MyTriggerDepth = 0;
7272

7373
/* Local function prototypes */
74+
static void renametrig_internal(Relation tgrel, Relation targetrel,
75+
HeapTuple trigtup, const char *newname,
76+
const char *expected_name);
77+
static void renametrig_partition(Relation tgrel, Oid partitionId,
78+
Oid parentTriggerOid, const char *newname,
79+
const char *expected_name);
7480
static void SetTriggerFlags(TriggerDesc *trigdesc, Trigger *trigger);
7581
static bool GetTupleForTrigger(EState *estate,
7682
EPQState *epqstate,
@@ -1442,38 +1448,16 @@ renametrig(RenameStmt *stmt)
14421448
targetrel = relation_open(relid, NoLock);
14431449

14441450
/*
1445-
* Scan pg_trigger twice for existing triggers on relation. We do this in
1446-
* order to ensure a trigger does not exist with newname (The unique index
1447-
* on tgrelid/tgname would complain anyway) and to ensure a trigger does
1448-
* exist with oldname.
1449-
*
1450-
* NOTE that this is cool only because we have AccessExclusiveLock on the
1451-
* relation, so the trigger set won't be changing underneath us.
1451+
* On partitioned tables, this operation recurses to partitions. Lock all
1452+
* tables upfront.
14521453
*/
1453-
tgrel = table_open(TriggerRelationId, RowExclusiveLock);
1454+
if (targetrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
1455+
(void) find_all_inheritors(relid, AccessExclusiveLock, NULL);
14541456

1455-
/*
1456-
* First pass -- look for name conflict
1457-
*/
1458-
ScanKeyInit(&key[0],
1459-
Anum_pg_trigger_tgrelid,
1460-
BTEqualStrategyNumber, F_OIDEQ,
1461-
ObjectIdGetDatum(relid));
1462-
ScanKeyInit(&key[1],
1463-
Anum_pg_trigger_tgname,
1464-
BTEqualStrategyNumber, F_NAMEEQ,
1465-
PointerGetDatum(stmt->newname));
1466-
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
1467-
NULL, 2, key);
1468-
if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
1469-
ereport(ERROR,
1470-
(errcode(ERRCODE_DUPLICATE_OBJECT),
1471-
errmsg("trigger \"%s\" for relation \"%s\" already exists",
1472-
stmt->newname, RelationGetRelationName(targetrel))));
1473-
systable_endscan(tgscan);
1457+
tgrel = table_open(TriggerRelationId, RowExclusiveLock);
14741458

14751459
/*
1476-
* Second pass -- look for trigger existing with oldname and update
1460+
* Search for the trigger to modify.
14771461
*/
14781462
ScanKeyInit(&key[0],
14791463
Anum_pg_trigger_tgrelid,
@@ -1489,27 +1473,40 @@ renametrig(RenameStmt *stmt)
14891473
{
14901474
Form_pg_trigger trigform;
14911475

1492-
/*
1493-
* Update pg_trigger tuple with new tgname.
1494-
*/
1495-
tuple = heap_copytuple(tuple); /* need a modifiable copy */
14961476
trigform = (Form_pg_trigger) GETSTRUCT(tuple);
14971477
tgoid = trigform->oid;
14981478

1499-
namestrcpy(&trigform->tgname,
1500-
stmt->newname);
1479+
/*
1480+
* If the trigger descends from a trigger on a parent partitioned
1481+
* table, reject the rename. We don't allow a trigger in a partition
1482+
* to differ in name from that of its parent: that would lead to an
1483+
* inconsistency that pg_dump would not reproduce.
1484+
*/
1485+
if (OidIsValid(trigform->tgparentid))
1486+
ereport(ERROR,
1487+
errmsg("cannot rename trigger \"%s\" on table \"%s\"",
1488+
stmt->subname, RelationGetRelationName(targetrel)),
1489+
errhint("Rename trigger on partitioned table \"%s\" instead.",
1490+
get_rel_name(get_partition_parent(relid, false))));
15011491

1502-
CatalogTupleUpdate(tgrel, &tuple->t_self, tuple);
15031492

1504-
InvokeObjectPostAlterHook(TriggerRelationId,
1505-
tgoid, 0);
1493+
/* Rename the trigger on this relation ... */
1494+
renametrig_internal(tgrel, targetrel, tuple, stmt->newname,
1495+
stmt->subname);
15061496

1507-
/*
1508-
* Invalidate relation's relcache entry so that other backends (and
1509-
* this one too!) are sent SI message to make them rebuild relcache
1510-
* entries. (Ideally this should happen automatically...)
1511-
*/
1512-
CacheInvalidateRelcache(targetrel);
1497+
/* ... and if it is partitioned, recurse to its partitions */
1498+
if (targetrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
1499+
{
1500+
PartitionDesc partdesc = RelationGetPartitionDesc(targetrel, true);
1501+
1502+
for (int i = 0; i < partdesc->nparts; i++)
1503+
{
1504+
Oid partitionId = partdesc->oids[i];
1505+
1506+
renametrig_partition(tgrel, partitionId, trigform->oid,
1507+
stmt->newname, stmt->subname);
1508+
}
1509+
}
15131510
}
15141511
else
15151512
{
@@ -1533,6 +1530,137 @@ renametrig(RenameStmt *stmt)
15331530
return address;
15341531
}
15351532

1533+
/*
1534+
* Subroutine for renametrig -- perform the actual work of renaming one
1535+
* trigger on one table.
1536+
*
1537+
* If the trigger has a name different from the expected one, raise a
1538+
* NOTICE about it.
1539+
*/
1540+
static void
1541+
renametrig_internal(Relation tgrel, Relation targetrel, HeapTuple trigtup,
1542+
const char *newname, const char *expected_name)
1543+
{
1544+
HeapTuple tuple;
1545+
Form_pg_trigger tgform;
1546+
ScanKeyData key[2];
1547+
SysScanDesc tgscan;
1548+
1549+
/* If the trigger already has the new name, nothing to do. */
1550+
tgform = (Form_pg_trigger) GETSTRUCT(trigtup);
1551+
if (strcmp(NameStr(tgform->tgname), newname) == 0)
1552+
return;
1553+
1554+
/*
1555+
* Before actually trying the rename, search for triggers with the same
1556+
* name. The update would fail with an ugly message in that case, and it
1557+
* is better to throw a nicer error.
1558+
*/
1559+
ScanKeyInit(&key[0],
1560+
Anum_pg_trigger_tgrelid,
1561+
BTEqualStrategyNumber, F_OIDEQ,
1562+
ObjectIdGetDatum(RelationGetRelid(targetrel)));
1563+
ScanKeyInit(&key[1],
1564+
Anum_pg_trigger_tgname,
1565+
BTEqualStrategyNumber, F_NAMEEQ,
1566+
PointerGetDatum(newname));
1567+
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
1568+
NULL, 2, key);
1569+
if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
1570+
ereport(ERROR,
1571+
(errcode(ERRCODE_DUPLICATE_OBJECT),
1572+
errmsg("trigger \"%s\" for relation \"%s\" already exists",
1573+
newname, RelationGetRelationName(targetrel))));
1574+
systable_endscan(tgscan);
1575+
1576+
/*
1577+
* The target name is free; update the existing pg_trigger tuple with it.
1578+
*/
1579+
tuple = heap_copytuple(trigtup); /* need a modifiable copy */
1580+
tgform = (Form_pg_trigger) GETSTRUCT(tuple);
1581+
1582+
/*
1583+
* If the trigger has a name different from what we expected, let the user
1584+
* know. (We can proceed anyway, since we must have reached here following
1585+
* a tgparentid link.)
1586+
*/
1587+
if (strcmp(NameStr(tgform->tgname), expected_name) != 0)
1588+
ereport(NOTICE,
1589+
errmsg("renamed trigger \"%s\" on relation \"%s\"",
1590+
NameStr(tgform->tgname),
1591+
RelationGetRelationName(targetrel)));
1592+
1593+
namestrcpy(&tgform->tgname, newname);
1594+
1595+
CatalogTupleUpdate(tgrel, &tuple->t_self, tuple);
1596+
1597+
InvokeObjectPostAlterHook(TriggerRelationId, tgform->oid, 0);
1598+
1599+
/*
1600+
* Invalidate relation's relcache entry so that other backends (and this
1601+
* one too!) are sent SI message to make them rebuild relcache entries.
1602+
* (Ideally this should happen automatically...)
1603+
*/
1604+
CacheInvalidateRelcache(targetrel);
1605+
}
1606+
1607+
/*
1608+
* Subroutine for renametrig -- Helper for recursing to partitions when
1609+
* renaming triggers on a partitioned table.
1610+
*/
1611+
static void
1612+
renametrig_partition(Relation tgrel, Oid partitionId, Oid parentTriggerOid,
1613+
const char *newname, const char *expected_name)
1614+
{
1615+
SysScanDesc tgscan;
1616+
ScanKeyData key;
1617+
HeapTuple tuple;
1618+
int found PG_USED_FOR_ASSERTS_ONLY = 0;
1619+
1620+
/*
1621+
* Given a relation and the OID of a trigger on parent relation, find the
1622+
* corresponding trigger in the child and rename that trigger to the given
1623+
* name.
1624+
*/
1625+
ScanKeyInit(&key,
1626+
Anum_pg_trigger_tgrelid,
1627+
BTEqualStrategyNumber, F_OIDEQ,
1628+
ObjectIdGetDatum(partitionId));
1629+
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
1630+
NULL, 1, &key);
1631+
while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
1632+
{
1633+
Form_pg_trigger tgform = (Form_pg_trigger) GETSTRUCT(tuple);
1634+
Relation partitionRel;
1635+
1636+
if (tgform->tgparentid != parentTriggerOid)
1637+
continue; /* not our trigger */
1638+
1639+
Assert(found++ <= 0);
1640+
1641+
partitionRel = table_open(partitionId, NoLock);
1642+
1643+
/* Rename the trigger on this partition */
1644+
renametrig_internal(tgrel, partitionRel, tuple, newname, expected_name);
1645+
1646+
/* And if this relation is partitioned, recurse to its partitions */
1647+
if (partitionRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
1648+
{
1649+
PartitionDesc partdesc = RelationGetPartitionDesc(partitionRel,
1650+
true);
1651+
1652+
for (int i = 0; i < partdesc->nparts; i++)
1653+
{
1654+
Oid partitionId = partdesc->oids[i];
1655+
1656+
renametrig_partition(tgrel, partitionId, tgform->oid, newname,
1657+
NameStr(tgform->tgname));
1658+
}
1659+
}
1660+
table_close(partitionRel, NoLock);
1661+
}
1662+
systable_endscan(tgscan);
1663+
}
15361664

15371665
/*
15381666
* EnableDisableTrigger()

src/test/regress/expected/triggers.out

+76
Original file line numberDiff line numberDiff line change
@@ -3410,3 +3410,79 @@ for each statement execute function trigger_function1();
34103410
delete from convslot_test_parent;
34113411
NOTICE: trigger = bdt_trigger, old_table = (111,tutu), (311,tutu)
34123412
drop table convslot_test_child, convslot_test_parent;
3413+
-- Test trigger renaming on partitioned tables
3414+
create table grandparent (id int, primary key (id)) partition by range (id);
3415+
create table middle partition of grandparent for values from (1) to (10)
3416+
partition by range (id);
3417+
create table chi partition of middle for values from (1) to (5);
3418+
create table cho partition of middle for values from (6) to (10);
3419+
create function f () returns trigger as
3420+
$$ begin return new; end; $$
3421+
language plpgsql;
3422+
create trigger a after insert on grandparent
3423+
for each row execute procedure f();
3424+
alter trigger a on grandparent rename to b;
3425+
select tgrelid::regclass, tgname,
3426+
(select tgname from pg_trigger tr where tr.oid = pg_trigger.tgparentid) parent_tgname
3427+
from pg_trigger where tgrelid in (select relid from pg_partition_tree('grandparent'))
3428+
order by tgname, tgrelid::regclass::text;
3429+
tgrelid | tgname | parent_tgname
3430+
-------------+--------+---------------
3431+
chi | b | b
3432+
cho | b | b
3433+
grandparent | b |
3434+
middle | b | b
3435+
(4 rows)
3436+
3437+
alter trigger a on only grandparent rename to b; -- ONLY not supported
3438+
ERROR: syntax error at or near "only"
3439+
LINE 1: alter trigger a on only grandparent rename to b;
3440+
^
3441+
alter trigger b on middle rename to c; -- can't rename trigger on partition
3442+
ERROR: cannot rename trigger "b" on table "middle"
3443+
HINT: Rename trigger on partitioned table "grandparent" instead.
3444+
create trigger c after insert on middle
3445+
for each row execute procedure f();
3446+
alter trigger b on grandparent rename to c;
3447+
ERROR: trigger "c" for relation "middle" already exists
3448+
-- Rename cascading does not affect statement triggers
3449+
create trigger p after insert on grandparent for each statement execute function f();
3450+
create trigger p after insert on middle for each statement execute function f();
3451+
alter trigger p on grandparent rename to q;
3452+
select tgrelid::regclass, tgname,
3453+
(select tgname from pg_trigger tr where tr.oid = pg_trigger.tgparentid) parent_tgname
3454+
from pg_trigger where tgrelid in (select relid from pg_partition_tree('grandparent'))
3455+
order by tgname, tgrelid::regclass::text;
3456+
tgrelid | tgname | parent_tgname
3457+
-------------+--------+---------------
3458+
chi | b | b
3459+
cho | b | b
3460+
grandparent | b |
3461+
middle | b | b
3462+
chi | c | c
3463+
cho | c | c
3464+
middle | c |
3465+
middle | p |
3466+
grandparent | q |
3467+
(9 rows)
3468+
3469+
drop table grandparent;
3470+
-- Trigger renaming does not recurse on legacy inheritance
3471+
create table parent (a int);
3472+
create table child () inherits (parent);
3473+
create trigger parenttrig after insert on parent
3474+
for each row execute procedure f();
3475+
create trigger parenttrig after insert on child
3476+
for each row execute procedure f();
3477+
alter trigger parenttrig on parent rename to anothertrig;
3478+
\d+ child
3479+
Table "public.child"
3480+
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
3481+
--------+---------+-----------+----------+---------+---------+--------------+-------------
3482+
a | integer | | | | plain | |
3483+
Triggers:
3484+
parenttrig AFTER INSERT ON child FOR EACH ROW EXECUTE FUNCTION f()
3485+
Inherits: parent
3486+
3487+
drop table parent, child;
3488+
drop function f();

0 commit comments

Comments
 (0)