@@ -125,6 +125,131 @@ have_createrole_privilege(void)
125125 return has_createrole_privilege (GetUserId ());
126126}
127127
128+ /*
129+ * check_drop_role_dependency
130+ *
131+ * Check if there are any dependencies associated with the role being dropped.
132+ *
133+ * This function scans the pg_auth_members table to find: 1) roles that are
134+ * admins of the role being dropped, and 2) roles where the role being dropped
135+ * is an admin. If both conditions are met, it means the roles that are admins
136+ * of the role being dropped rely on it for admin privileges on roles where the
137+ * role being dropped is an admin.
138+ *
139+ * For example, if userA creates userB, and userB creates userC, both userB and
140+ * userA would have admin privileges on userC. UserB has admin privileges on
141+ * userC because it created userC, while userA inherits those admin privileges
142+ * from userB. This means userA relies on userB for admin privileges on userC.
143+ * Therefore, if userB is dropped, userA will lose its admin privileges on
144+ * userC.
145+ *
146+ * If such a dependency exists, this function raises an error to prevent the
147+ * role from being dropped.
148+ *
149+ * pg_auth_members_rel: relation object representing the pg_auth_members system
150+ * catalog.
151+ * roleid: Oid of the role that is being dropped.
152+ */
153+ static void
154+ check_drop_role_dependency (Relation pg_auth_members_rel , Oid roleid )
155+ {
156+ ScanKeyData scankey ;
157+ SysScanDesc sscan ;
158+ HeapTuple tmp_tuple ;
159+ List * admin_members_of_drop_role = NULL ; /* List of roles that are admin members of the role to drop */
160+ List * roles_with_drop_role_as_admin_member = NULL ; /* List of roles where the role to drop is an admin member */
161+
162+ ScanKeyInit (& scankey ,
163+ Anum_pg_auth_members_roleid ,
164+ BTEqualStrategyNumber , F_OIDEQ ,
165+ ObjectIdGetDatum (roleid ));
166+
167+ sscan = systable_beginscan (pg_auth_members_rel ,
168+ AuthMemRoleMemIndexId ,
169+ true, NULL , 1 , & scankey );
170+
171+ while (HeapTupleIsValid (tmp_tuple = systable_getnext (sscan )))
172+ {
173+ Form_pg_auth_members authmem_form ;
174+ authmem_form = (Form_pg_auth_members ) GETSTRUCT (tmp_tuple );
175+
176+ if (authmem_form -> admin_option )
177+ {
178+ if (!admin_members_of_drop_role )
179+ admin_members_of_drop_role =
180+ list_make1_oid (authmem_form -> member );
181+ else
182+ admin_members_of_drop_role =
183+ lappend_oid (admin_members_of_drop_role ,
184+ authmem_form -> member );
185+ }
186+ }
187+
188+ systable_endscan (sscan );
189+
190+ ScanKeyInit (& scankey ,
191+ Anum_pg_auth_members_member ,
192+ BTEqualStrategyNumber , F_OIDEQ ,
193+ ObjectIdGetDatum (roleid ));
194+
195+ sscan = systable_beginscan (pg_auth_members_rel ,
196+ AuthMemRoleMemIndexId ,
197+ true, NULL , 1 , & scankey );
198+
199+ while (HeapTupleIsValid (tmp_tuple = systable_getnext (sscan )))
200+ {
201+ Form_pg_auth_members authmem_form ;
202+ authmem_form = (Form_pg_auth_members ) GETSTRUCT (tmp_tuple );
203+
204+ if (authmem_form -> admin_option )
205+ {
206+ if (!roles_with_drop_role_as_admin_member )
207+ roles_with_drop_role_as_admin_member =
208+ list_make1_oid (authmem_form -> roleid );
209+ else
210+ roles_with_drop_role_as_admin_member =
211+ lappend_oid (roles_with_drop_role_as_admin_member ,
212+ authmem_form -> roleid );
213+ }
214+ }
215+
216+ systable_endscan (sscan );
217+
218+ /* If there are dependencies, raise an error */
219+ if (admin_members_of_drop_role && roles_with_drop_role_as_admin_member )
220+ {
221+ ListCell * dependent_role ;
222+ ListCell * referenced_role ;
223+ StringInfoData detail_log ;
224+
225+ /* Initialize StringInfo to build the detailed error log message */
226+ initStringInfo (& detail_log );
227+
228+ foreach (dependent_role , admin_members_of_drop_role )
229+ {
230+ Oid dependent_role_oid = lfirst_oid (dependent_role );
231+
232+ foreach (referenced_role , roles_with_drop_role_as_admin_member )
233+ {
234+ Oid referenced_role_oid = lfirst_oid (referenced_role );
235+
236+ if (detail_log .len > 0 )
237+ appendStringInfoChar (& detail_log , '\n' );
238+
239+ appendStringInfo (& detail_log , "role %s inherits ADMIN privileges on role %s through role %s" ,
240+ GetUserNameFromId (dependent_role_oid , false),
241+ GetUserNameFromId (referenced_role_oid , false),
242+ GetUserNameFromId (roleid , false));
243+ }
244+ }
245+
246+ ereport (ERROR ,
247+ (errcode (ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST ),
248+ errmsg ("role \"%s\" cannot be dropped because some objects depend on it" ,
249+ GetUserNameFromId (roleid , false)),
250+ errdetail ("%s" , detail_log .data )));
251+ }
252+ }
128253
129254/*
130255 * CREATE ROLE
@@ -1190,6 +1315,12 @@ DropRole(DropRoleStmt *stmt)
11901315 */
11911316 LockSharedObject (AuthIdRelationId , roleid , 0 , AccessExclusiveLock );
11921317
1318+ /*
1319+ * Check if there are any existing dependencies on the role before dropping
1320+ * it.
1321+ */
1322+ check_drop_role_dependency (pg_auth_members_rel , roleid );
1323+
11931324 /*
11941325 * If there is a pg_auth_members entry that has one of the roles to be
11951326 * dropped as the roleid or member, it should be silently removed, but
0 commit comments