@@ -187,10 +187,13 @@ static void ApplyAddCitusDependedObjectsToDependencyList(ObjectAddressCollector
187187static List * GetViewRuleReferenceDependencyList (Oid relationId );
188188static List * ExpandCitusSupportedTypes (ObjectAddressCollector * collector ,
189189 ObjectAddress target );
190+ static List * ExpandCitusSupportedTypesForNodeActivation (ObjectAddressCollector *
191+ collector ,
192+ ObjectAddress target );
190193static List * ExpandForPgVanilla (ObjectAddressCollector * collector ,
191194 ObjectAddress target );
192195static List * GetDependentRoleIdsFDW (Oid FDWOid );
193- static List * ExpandRolesToGroups (Oid roleid );
196+ static List * ExpandRolesToGroups (Oid roleid , bool includeGrantors );
194197static ViewDependencyNode * BuildViewDependencyGraph (Oid relationId , HTAB * nodeMap );
195198static bool IsObjectAddressOwnedByExtension (const ObjectAddress * target ,
196199 ObjectAddress * extensionAddress );
@@ -344,7 +347,7 @@ OrderObjectAddressListInDependencyOrder(List *objectAddressList)
344347 }
345348
346349 RecurseObjectDependencies (* objectAddress ,
347- & ExpandCitusSupportedTypes ,
350+ & ExpandCitusSupportedTypesForNodeActivation ,
348351 & FollowAllSupportedDependencies ,
349352 & ApplyAddToDependencyList ,
350353 & collector );
@@ -1545,9 +1548,15 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe
15451548 {
15461549 /*
15471550 * Roles are members of other roles. These relations are not recorded directly
1548- * but can be deduced from pg_auth_members
1551+ * but can be deduced from pg_auth_members.
1552+ *
1553+ * Note: we intentionally do NOT include grantors here. Grantors are
1554+ * only relevant for ordering role creation during node activation
1555+ * (see ExpandCitusSupportedTypesForNodeActivation). Including them
1556+ * in the generic dependency graph would produce false-positive
1557+ * circular-dependency errors for legitimate mutual GRANTs.
15491558 */
1550- return ExpandRolesToGroups (target .objectId );
1559+ return ExpandRolesToGroups (target .objectId , false );
15511560 }
15521561
15531562 case ExtensionRelationId :
@@ -1737,6 +1746,32 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe
17371746}
17381747
17391748
1749+ /*
1750+ * ExpandCitusSupportedTypesForNodeActivation is a variant of
1751+ * ExpandCitusSupportedTypes used only when ordering distributed objects for
1752+ * propagation to a newly-activated node. For roles, it additionally treats
1753+ * grantors of membership tuples as dependencies so that a role used as a
1754+ * grantor is created on the new node before the grantee role. This is what
1755+ * fixes issue #8425.
1756+ *
1757+ * This expansion must NOT be used by the generic dependency-collection paths
1758+ * (creation, cycle-detection, DDL propagation) because mutually-granted roles
1759+ * would appear to form a cycle and be rejected by
1760+ * DeferErrorIfCircularDependencyExists.
1761+ */
1762+ static List *
1763+ ExpandCitusSupportedTypesForNodeActivation (ObjectAddressCollector * collector ,
1764+ ObjectAddress target )
1765+ {
1766+ if (target .classId == AuthIdRelationId )
1767+ {
1768+ return ExpandRolesToGroups (target .objectId , true);
1769+ }
1770+
1771+ return ExpandCitusSupportedTypes (collector , target );
1772+ }
1773+
1774+
17401775/*
17411776 * ExpandForPgVanilla only expands only comosite types because other types
17421777 * will find their dependencies in pg_depend. The method should only be called by
@@ -1800,10 +1835,19 @@ GetDependentRoleIdsFDW(Oid FDWOid)
18001835
18011836/*
18021837 * ExpandRolesToGroups returns a list of object addresses pointing to roles that roleid
1803- * depends on.
1838+ * depends on. This always includes:
1839+ * 1. Roles that roleid is a member of (membership->roleid)
1840+ *
1841+ * When includeGrantors is true, it additionally includes:
1842+ * 2. Roles that are used as grantors for roleid's memberships (membership->grantor)
1843+ *
1844+ * The grantor dependency is only used for ordering role propagation during node
1845+ * activation (see ExpandCitusSupportedTypesForNodeActivation). It must NOT be used
1846+ * in the generic dependency graph because legitimate mutual GRANTs between roles
1847+ * would otherwise be reported as circular dependencies.
18041848 */
18051849static List *
1806- ExpandRolesToGroups (Oid roleid )
1850+ ExpandRolesToGroups (Oid roleid , bool includeGrantors )
18071851{
18081852 Relation pgAuthMembers = table_open (AuthMemRelationId , AccessShareLock );
18091853 HeapTuple tuple = NULL ;
@@ -1819,15 +1863,47 @@ ExpandRolesToGroups(Oid roleid)
18191863 true, NULL , scanKeyCount , scanKey );
18201864
18211865 List * roles = NIL ;
1866+
1867+ /*
1868+ * Track all role OIDs we have already emitted as dependencies so that
1869+ * parent roles and grantors are de-duplicated through a single set.
1870+ * A role can appear multiple times in pg_auth_members for the same
1871+ * member (different grantors), and the same OID may show up as both a
1872+ * parent role and a grantor; one DependencyDefinition per OID is enough.
1873+ *
1874+ * Note: For roles with many memberships this O(n) membership check could
1875+ * be replaced with a hash set, but in practice the number of memberships
1876+ * per role is small.
1877+ */
1878+ List * seenRoleIds = NIL ;
18221879 while ((tuple = systable_getnext (scanDescriptor )) != NULL )
18231880 {
18241881 Form_pg_auth_members membership = (Form_pg_auth_members ) GETSTRUCT (tuple );
18251882
1826- DependencyDefinition * definition = palloc0 (sizeof (DependencyDefinition ));
1827- definition -> mode = DependencyObjectAddress ;
1828- ObjectAddressSet (definition -> data .address , AuthIdRelationId , membership -> roleid );
1883+ Oid candidates [2 ] = { membership -> roleid , membership -> grantor };
1884+ int numCandidates = includeGrantors ? 2 : 1 ;
1885+ for (int i = 0 ; i < numCandidates ; i ++ )
1886+ {
1887+ Oid candidateOid = candidates [i ];
18291888
1830- roles = lappend (roles , definition );
1889+ /*
1890+ * Skip self-references: a role cannot depend on itself (the
1891+ * parent-role case cannot hit this because pg_auth_members does
1892+ * not allow roleid == member, but the grantor case can).
1893+ */
1894+ if (candidateOid == roleid ||
1895+ !OidIsValid (candidateOid ) ||
1896+ list_member_oid (seenRoleIds , candidateOid ))
1897+ {
1898+ continue ;
1899+ }
1900+
1901+ DependencyDefinition * definition = palloc0 (sizeof (DependencyDefinition ));
1902+ definition -> mode = DependencyObjectAddress ;
1903+ ObjectAddressSet (definition -> data .address , AuthIdRelationId , candidateOid );
1904+ roles = lappend (roles , definition );
1905+ seenRoleIds = lappend_oid (seenRoleIds , candidateOid );
1906+ }
18311907 }
18321908
18331909 systable_endscan (scanDescriptor );
0 commit comments