@@ -71,6 +71,12 @@ int SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN;
7171static 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 );
7480static void SetTriggerFlags (TriggerDesc * trigdesc , Trigger * trigger );
7581static 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()
0 commit comments