</thead>
<tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>roleid</structfield> <type>oid</type>
relationId == AuthIdRolnameIndexId ||
relationId == AuthMemMemRoleIndexId ||
relationId == AuthMemRoleMemIndexId ||
+ relationId == AuthMemOidIndexId ||
+ relationId == AuthMemGrantorIndexId ||
relationId == DatabaseNameIndexId ||
relationId == DatabaseOidIndexId ||
relationId == DbRoleSettingDatidRolidIndexId ||
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_members.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
TSTemplateRelationId, /* OCLASS_TSTEMPLATE */
TSConfigRelationId, /* OCLASS_TSCONFIG */
AuthIdRelationId, /* OCLASS_ROLE */
+ AuthMemRelationId, /* OCLASS_ROLE_MEMBERSHIP */
DatabaseRelationId, /* OCLASS_DATABASE */
TableSpaceRelationId, /* OCLASS_TBLSPACE */
ForeignDataWrapperRelationId, /* OCLASS_FDW */
case OCLASS_DEFACL:
case OCLASS_EVENT_TRIGGER:
case OCLASS_TRANSFORM:
+ case OCLASS_ROLE_MEMBERSHIP:
DropObjectById(object);
break;
* Accepts the same flags as performDeletion (though currently only
* PERFORM_DELETION_CONCURRENTLY does anything).
*
- * We use LockRelation for relations, LockDatabaseObject for everything
- * else. Shared-across-databases objects are not currently supported
- * because no caller cares, but could be modified to use LockSharedObject.
+ * We use LockRelation for relations, and otherwise LockSharedObject or
+ * LockDatabaseObject as appropriate for the object type.
*/
void
AcquireDeletionLock(const ObjectAddress *object, int flags)
else
LockRelationOid(object->objectId, AccessExclusiveLock);
}
+ else if (object->classId == AuthMemRelationId)
+ LockSharedObject(object->classId, object->objectId, 0,
+ AccessExclusiveLock);
else
{
/* assume we should lock the whole object not a sub-object */
case AuthIdRelationId:
return OCLASS_ROLE;
+ case AuthMemRelationId:
+ return OCLASS_ROLE_MEMBERSHIP;
+
case DatabaseRelationId:
return OCLASS_DATABASE;
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_members.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
-1,
true
},
+ {
+ "role membership",
+ AuthMemRelationId,
+ AuthMemOidIndexId,
+ -1,
+ -1,
+ Anum_pg_auth_members_oid,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ Anum_pg_auth_members_grantor,
+ InvalidAttrNumber,
+ -1,
+ true
+ },
{
"rule",
RewriteRelationId,
{
"role", OBJECT_ROLE
},
+ /* OCLASS_ROLE_MEMBERSHIP */
+ {
+ "role membership", -1 /* unmapped */
+ },
/* OCLASS_DATABASE */
{
"database", OBJECT_DATABASE
break;
}
+ case OCLASS_ROLE_MEMBERSHIP:
+ {
+ Relation amDesc;
+ ScanKeyData skey[1];
+ SysScanDesc rcscan;
+ HeapTuple tup;
+ Form_pg_auth_members amForm;
+
+ amDesc = table_open(AuthMemRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_auth_members_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->objectId));
+
+ rcscan = systable_beginscan(amDesc, AuthMemOidIndexId, true,
+ NULL, 1, skey);
+
+ tup = systable_getnext(rcscan);
+
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "could not find tuple for role membership %u",
+ object->objectId);
+
+ systable_endscan(rcscan);
+ table_close(amDesc, AccessShareLock);
+ break;
+ }
+
+ amForm = (Form_pg_auth_members) GETSTRUCT(tup);
+
+ appendStringInfo(&buffer, _("membership of role %s in role %s"),
+ GetUserNameFromId(amForm->member, false),
+ GetUserNameFromId(amForm->roleid, false));
+
+ systable_endscan(rcscan);
+ table_close(amDesc, AccessShareLock);
+ break;
+ }
+
case OCLASS_DATABASE:
{
char *datname;
appendStringInfoString(&buffer, "role");
break;
+ case OCLASS_ROLE_MEMBERSHIP:
+ appendStringInfoString(&buffer, "role membership");
+ break;
+
case OCLASS_DATABASE:
appendStringInfoString(&buffer, "database");
break;
break;
}
+ case OCLASS_ROLE_MEMBERSHIP:
+ {
+ Relation authMemDesc;
+ ScanKeyData skey[1];
+ SysScanDesc amscan;
+ HeapTuple tup;
+ Form_pg_auth_members amForm;
+
+ authMemDesc = table_open(AuthMemRelationId,
+ AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_auth_members_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->objectId));
+
+ amscan = systable_beginscan(authMemDesc, AuthMemOidIndexId, true,
+ NULL, 1, skey);
+
+ tup = systable_getnext(amscan);
+
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "could not find tuple for pg_auth_members entry %u",
+ object->objectId);
+
+ systable_endscan(amscan);
+ table_close(authMemDesc, AccessShareLock);
+ break;
+ }
+
+ amForm = (Form_pg_auth_members) GETSTRUCT(tup);
+
+ appendStringInfo(&buffer, _("membership of role %s in role %s"),
+ GetUserNameFromId(amForm->member, false),
+ GetUserNameFromId(amForm->roleid, false));
+
+ systable_endscan(amscan);
+ table_close(authMemDesc, AccessShareLock);
+ break;
+ }
+
case OCLASS_DATABASE:
{
char *datname;
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_members.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
case SHARED_DEPENDENCY_INVALID:
elog(ERROR, "unexpected dependency type");
break;
- case SHARED_DEPENDENCY_ACL:
- RemoveRoleFromObjectACL(roleid,
- sdepForm->classid,
- sdepForm->objid);
- break;
case SHARED_DEPENDENCY_POLICY:
/*
add_exact_object_address(&obj, deleteobjs);
}
break;
+ case SHARED_DEPENDENCY_ACL:
+
+ /*
+ * Dependencies on role grants are recorded using
+ * SHARED_DEPENDENCY_ACL, but unlike a regular ACL list
+ * which stores all permissions for a particular object in
+ * a single ACL array, there's a separate catalog row for
+ * each grant - so removing the grant just means removing
+ * the entire row.
+ */
+ if (sdepForm->classid != AuthMemRelationId)
+ {
+ RemoveRoleFromObjectACL(roleid,
+ sdepForm->classid,
+ sdepForm->objid);
+ break;
+ }
+ /* FALLTHROUGH */
case SHARED_DEPENDENCY_OWNER:
- /* If a local object, save it for deletion below */
- if (sdepForm->dbid == MyDatabaseId)
+ /* Save it for deletion below */
+ obj.classId = sdepForm->classid;
+ obj.objectId = sdepForm->objid;
+ obj.objectSubId = sdepForm->objsubid;
+ /* as above */
+ AcquireDeletionLock(&obj, 0);
+ if (!systable_recheck_tuple(scan, tuple))
{
- obj.classId = sdepForm->classid;
- obj.objectId = sdepForm->objid;
- obj.objectSubId = sdepForm->objsubid;
- /* as above */
- AcquireDeletionLock(&obj, 0);
- if (!systable_recheck_tuple(scan, tuple))
- {
- ReleaseDeletionLock(&obj);
- break;
- }
- add_exact_object_address(&obj, deleteobjs);
+ ReleaseDeletionLock(&obj);
+ break;
}
+ add_exact_object_address(&obj, deleteobjs);
break;
}
}
SELECT
rolname AS groname,
oid AS grosysid,
- ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
+ ARRAY(SELECT member FROM pg_auth_members WHERE roleid = pg_authid.oid) AS grolist
FROM pg_authid
WHERE NOT rolcanlogin;
case OCLASS_TRIGGER:
case OCLASS_SCHEMA:
case OCLASS_ROLE:
+ case OCLASS_ROLE_MEMBERSHIP:
case OCLASS_DATABASE:
case OCLASS_TBLSPACE:
case OCLASS_FDW:
case OCLASS_DATABASE:
case OCLASS_TBLSPACE:
case OCLASS_ROLE:
+ case OCLASS_ROLE_MEMBERSHIP:
case OCLASS_PARAMETER_ACL:
/* no support for global objects */
return false;
case OCLASS_TSTEMPLATE:
case OCLASS_TSCONFIG:
case OCLASS_ROLE:
+ case OCLASS_ROLE_MEMBERSHIP:
case OCLASS_DATABASE:
case OCLASS_TBLSPACE:
case OCLASS_FDW:
Relation pg_authid_rel,
pg_auth_members_rel;
ListCell *item;
+ List *role_addresses = NIL;
if (!have_createrole_privilege())
ereport(ERROR,
/*
* Scan the pg_authid relation to find the Oid of the role(s) to be
- * deleted.
+ * deleted and perform preliminary permissions and sanity checks.
*/
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
pg_auth_members_rel = table_open(AuthMemRelationId, RowExclusiveLock);
tmp_tuple;
Form_pg_authid roleform;
ScanKeyData scankey;
- char *detail;
- char *detail_log;
SysScanDesc sscan;
Oid roleid;
+ ObjectAddress *role_address;
if (rolspec->roletype != ROLESPEC_CSTRING)
ereport(ERROR,
/* DROP hook for the role being removed */
InvokeObjectDropHook(AuthIdRelationId, roleid, 0);
+ /* Don't leak the syscache tuple */
+ ReleaseSysCache(tuple);
+
/*
* Lock the role, so nobody can add dependencies to her while we drop
* her. We keep the lock until the end of transaction.
*/
LockSharedObject(AuthIdRelationId, roleid, 0, AccessExclusiveLock);
- /* Check for pg_shdepend entries depending on this role */
- if (checkSharedDependencies(AuthIdRelationId, roleid,
- &detail, &detail_log))
- ereport(ERROR,
- (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
- errmsg("role \"%s\" cannot be dropped because some objects depend on it",
- role),
- errdetail_internal("%s", detail),
- errdetail_log("%s", detail_log)));
-
- /*
- * Remove the role from the pg_authid table
- */
- CatalogTupleDelete(pg_authid_rel, &tuple->t_self);
-
- ReleaseSysCache(tuple);
-
/*
- * Remove role from the pg_auth_members table. We have to remove all
- * tuples that show it as either a role or a member.
+ * If there is a pg_auth_members entry that has one of the roles to be
+ * dropped as the roleid or member, it should be silently removed, but
+ * if there is a pg_auth_members entry that has one of the roles to be
+ * dropped as the grantor, the operation should fail.
+ *
+ * It's possible, however, that a single pg_auth_members entry could
+ * fall into multiple categories - e.g. the user could do "GRANT foo
+ * TO bar GRANTED BY baz" and then "DROP ROLE baz, bar". We want such
+ * an operation to succeed regardless of the order in which the
+ * to-be-dropped roles are passed to DROP ROLE.
*
- * XXX what about grantor entries? Maybe we should do one heap scan.
+ * To make that work, we remove all pg_auth_members entries that can
+ * be silently removed in this loop, and then below we'll make a
+ * second pass over the list of roles to be removed and check for any
+ * remaining dependencies.
*/
ScanKeyInit(&scankey,
Anum_pg_auth_members_roleid,
while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
{
+ Form_pg_auth_members authmem_form;
+
+ authmem_form = (Form_pg_auth_members) GETSTRUCT(tmp_tuple);
+ deleteSharedDependencyRecordsFor(AuthMemRelationId,
+ authmem_form->oid, 0);
CatalogTupleDelete(pg_auth_members_rel, &tmp_tuple->t_self);
}
while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
{
+ Form_pg_auth_members authmem_form;
+
+ authmem_form = (Form_pg_auth_members) GETSTRUCT(tmp_tuple);
+ deleteSharedDependencyRecordsFor(AuthMemRelationId,
+ authmem_form->oid, 0);
CatalogTupleDelete(pg_auth_members_rel, &tmp_tuple->t_self);
}
systable_endscan(sscan);
- /*
- * Remove any comments or security labels on this role.
- */
- DeleteSharedComments(roleid, AuthIdRelationId);
- DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
-
- /*
- * Remove settings for this role.
- */
- DropSetting(InvalidOid, roleid);
-
/*
* Advance command counter so that later iterations of this loop will
* see the changes already made. This is essential if, for example,
* itself.)
*/
CommandCounterIncrement();
+
+ /* Looks tentatively OK, add it to the list. */
+ role_address = palloc(sizeof(ObjectAddress));
+ role_address->classId = AuthIdRelationId;
+ role_address->objectId = roleid;
+ role_address->objectSubId = 0;
+ role_addresses = lappend(role_addresses, role_address);
+ }
+
+ /*
+ * Second pass over the roles to be removed.
+ */
+ foreach(item, role_addresses)
+ {
+ ObjectAddress *role_address = lfirst(item);
+ Oid roleid = role_address->objectId;
+ HeapTuple tuple;
+ Form_pg_authid roleform;
+ char *detail;
+ char *detail_log;
+
+ /*
+ * Re-find the pg_authid tuple.
+ *
+ * Since we've taken a lock on the role OID, it shouldn't be possible
+ * for the tuple to have been deleted -- or for that matter updated --
+ * unless the user is manually modifying the system catalogs.
+ */
+ tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for role %u", roleid);
+ roleform = (Form_pg_authid) GETSTRUCT(tuple);
+
+ /*
+ * Check for pg_shdepend entries depending on this role.
+ *
+ * This needs to happen after we've completed removing any
+ * pg_auth_members entries that can be removed silently, in order to
+ * avoid spurious failures. See notes above for more details.
+ */
+ if (checkSharedDependencies(AuthIdRelationId, roleid,
+ &detail, &detail_log))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("role \"%s\" cannot be dropped because some objects depend on it",
+ NameStr(roleform->rolname)),
+ errdetail_internal("%s", detail),
+ errdetail_log("%s", detail_log)));
+
+ /*
+ * Remove the role from the pg_authid table
+ */
+ CatalogTupleDelete(pg_authid_rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+
+ /*
+ * Remove any comments or security labels on this role.
+ */
+ DeleteSharedComments(roleid, AuthIdRelationId);
+ DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
+
+ /*
+ * Remove settings for this role.
+ */
+ DropSetting(InvalidOid, roleid);
}
/*
Datum new_record[Natts_pg_auth_members] = {0};
bool new_record_nulls[Natts_pg_auth_members] = {0};
bool new_record_repl[Natts_pg_auth_members] = {0};
+ Form_pg_auth_members authmem_form;
/*
* pg_database_owner is never a role member. Lifting this restriction
authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM,
ObjectIdGetDatum(roleid),
ObjectIdGetDatum(memberid));
- if (HeapTupleIsValid(authmem_tuple) &&
- (!admin_opt ||
- ((Form_pg_auth_members) GETSTRUCT(authmem_tuple))->admin_option))
+ if (!HeapTupleIsValid(authmem_tuple))
{
- ereport(NOTICE,
- (errmsg("role \"%s\" is already a member of role \"%s\"",
- get_rolespec_name(memberRole), rolename)));
- ReleaseSysCache(authmem_tuple);
- continue;
+ authmem_form = NULL;
+ }
+ else
+ {
+ authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
+
+ if (!admin_opt || authmem_form->admin_option)
+ {
+ ereport(NOTICE,
+ (errmsg("role \"%s\" is already a member of role \"%s\"",
+ get_rolespec_name(memberRole), rolename)));
+ ReleaseSysCache(authmem_tuple);
+ continue;
+ }
}
/* Build a tuple to insert or update */
new_record,
new_record_nulls, new_record_repl);
CatalogTupleUpdate(pg_authmem_rel, &tuple->t_self, tuple);
+
+ if (authmem_form->grantor != grantorId)
+ {
+ Oid *oldmembers = palloc(sizeof(Oid));
+ Oid *newmembers = palloc(sizeof(Oid));
+
+ /* updateAclDependencies wants to pfree array inputs */
+ oldmembers[0] = authmem_form->grantor;
+ newmembers[0] = grantorId;
+
+ updateAclDependencies(AuthMemRelationId, authmem_form->oid,
+ 0, InvalidOid,
+ 1, oldmembers,
+ 1, newmembers);
+ }
+
ReleaseSysCache(authmem_tuple);
}
else
{
+ Oid objectId;
+ Oid *newmembers = palloc(sizeof(Oid));
+
+ objectId = GetNewObjectId();
+ new_record[Anum_pg_auth_members_oid - 1] = objectId;
tuple = heap_form_tuple(pg_authmem_dsc,
new_record, new_record_nulls);
CatalogTupleInsert(pg_authmem_rel, tuple);
+
+ /* updateAclDependencies wants to pfree array inputs */
+ newmembers[0] = grantorId;
+ updateAclDependencies(AuthMemRelationId, objectId,
+ 0, InvalidOid,
+ 0, NULL,
+ 1, newmembers);
}
/* CCI after each change, in case there are duplicates in list */
RoleSpec *memberRole = lfirst(specitem);
Oid memberid = lfirst_oid(iditem);
HeapTuple authmem_tuple;
+ Form_pg_auth_members authmem_form;
/*
* Find entry for this role/member
continue;
}
+ authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
+
if (!admin_opt)
{
- /* Remove the entry altogether */
+ /*
+ * Remove the entry altogether, after first removing its
+ * dependencies
+ */
+ deleteSharedDependencyRecordsFor(AuthMemRelationId,
+ authmem_form->oid, 0);
CatalogTupleDelete(pg_authmem_rel, &authmem_tuple->t_self);
}
else
OCLASS_TSTEMPLATE, /* pg_ts_template */
OCLASS_TSCONFIG, /* pg_ts_config */
OCLASS_ROLE, /* pg_authid */
+ OCLASS_ROLE_MEMBERSHIP, /* pg_auth_members */
OCLASS_DATABASE, /* pg_database */
OCLASS_TBLSPACE, /* pg_tablespace */
OCLASS_FDW, /* pg_foreign_data_wrapper */
*/
CATALOG(pg_auth_members,1261,AuthMemRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2843,AuthMemRelation_Rowtype_Id) BKI_SCHEMA_MACRO
{
+ Oid oid; /* oid */
Oid roleid BKI_LOOKUP(pg_authid); /* ID of a role */
Oid member BKI_LOOKUP(pg_authid); /* ID of a member of that role */
Oid grantor BKI_LOOKUP(pg_authid); /* who granted the membership */
*/
typedef FormData_pg_auth_members *Form_pg_auth_members;
-DECLARE_UNIQUE_INDEX_PKEY(pg_auth_members_role_member_index, 2694, AuthMemRoleMemIndexId, on pg_auth_members using btree(roleid oid_ops, member oid_ops));
+DECLARE_UNIQUE_INDEX_PKEY(pg_auth_members_oid_index, 9385, AuthMemOidIndexId, on pg_auth_members using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_auth_members_role_member_index, 2694, AuthMemRoleMemIndexId, on pg_auth_members using btree(roleid oid_ops, member oid_ops));
DECLARE_UNIQUE_INDEX(pg_auth_members_member_role_index, 2695, AuthMemMemRoleIndexId, on pg_auth_members using btree(member oid_ops, roleid oid_ops));
+DECLARE_INDEX(pg_auth_members_grantor_index, 9384, AuthMemGrantorIndexId, on pg_auth_members using btree(grantor oid_ops));
#endif /* PG_AUTH_MEMBERS_H */
DROP ROLE regress_nosuch_admin_recursive;
ERROR: role "regress_nosuch_admin_recursive" does not exist
DROP ROLE regress_plainrole;
+-- fail, can't drop regress_createrole yet, due to outstanding grants
+DROP ROLE regress_createrole;
+ERROR: role "regress_createrole" cannot be dropped because some objects depend on it
+DETAIL: privileges for membership of role regress_read_all_data in role pg_read_all_data
+privileges for membership of role regress_write_all_data in role pg_write_all_data
+privileges for membership of role regress_monitor in role pg_monitor
+privileges for membership of role regress_read_all_settings in role pg_read_all_settings
+privileges for membership of role regress_read_all_stats in role pg_read_all_stats
+privileges for membership of role regress_stat_scan_tables in role pg_stat_scan_tables
+privileges for membership of role regress_read_server_files in role pg_read_server_files
+privileges for membership of role regress_write_server_files in role pg_write_server_files
+privileges for membership of role regress_execute_server_program in role pg_execute_server_program
+privileges for membership of role regress_signal_backend in role pg_signal_backend
-- ok, should be able to drop non-superuser roles we created
DROP ROLE regress_createdb;
-DROP ROLE regress_createrole;
DROP ROLE regress_login;
DROP ROLE regress_inherit;
DROP ROLE regress_connection_limit;
DROP ROLE regress_write_server_files;
DROP ROLE regress_execute_server_program;
DROP ROLE regress_signal_backend;
+-- ok, dropped the other roles first so this is ok now
+DROP ROLE regress_createrole;
-- fail, role still owns database objects
DROP ROLE regress_tenant;
ERROR: role "regress_tenant" cannot be dropped because some objects depend on it
CREATE USER regress_priv_user9;
CREATE USER regress_priv_user10;
CREATE ROLE regress_priv_role;
+-- test GRANTED BY with DROP OWNED and REASSIGN OWNED
+GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION;
+GRANT regress_priv_user1 TO regress_priv_user3 GRANTED BY regress_priv_user2;
+DROP ROLE regress_priv_user2; -- fail, dependency
+ERROR: role "regress_priv_user2" cannot be dropped because some objects depend on it
+DETAIL: privileges for membership of role regress_priv_user3 in role regress_priv_user1
+REASSIGN OWNED BY regress_priv_user2 TO regress_priv_user4;
+DROP ROLE regress_priv_user2; -- still fail, REASSIGN OWNED doesn't help
+ERROR: role "regress_priv_user2" cannot be dropped because some objects depend on it
+DETAIL: privileges for membership of role regress_priv_user3 in role regress_priv_user1
+DROP OWNED BY regress_priv_user2;
+DROP ROLE regress_priv_user2; -- ok now, DROP OWNED does the job
+-- test that removing granted role or grantee role removes dependency
+GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION;
+GRANT regress_priv_user1 TO regress_priv_user4 GRANTED BY regress_priv_user3;
+DROP ROLE regress_priv_user3; -- should fail, dependency
+ERROR: role "regress_priv_user3" cannot be dropped because some objects depend on it
+DETAIL: privileges for membership of role regress_priv_user4 in role regress_priv_user1
+DROP ROLE regress_priv_user4; -- ok
+DROP ROLE regress_priv_user3; -- ok now
+GRANT regress_priv_user1 TO regress_priv_user5 WITH ADMIN OPTION;
+GRANT regress_priv_user1 TO regress_priv_user6 GRANTED BY regress_priv_user5;
+DROP ROLE regress_priv_user5; -- should fail, dependency
+ERROR: role "regress_priv_user5" cannot be dropped because some objects depend on it
+DETAIL: privileges for membership of role regress_priv_user6 in role regress_priv_user1
+DROP ROLE regress_priv_user1, regress_priv_user5; -- ok, despite order
+-- recreate the roles we just dropped
+CREATE USER regress_priv_user1;
+CREATE USER regress_priv_user2;
+CREATE USER regress_priv_user3;
+CREATE USER regress_priv_user4;
+CREATE USER regress_priv_user5;
GRANT pg_read_all_data TO regress_priv_user6;
GRANT pg_write_all_data TO regress_priv_user7;
GRANT pg_read_all_settings TO regress_priv_user8 WITH ADMIN OPTION;
DROP ROLE regress_nosuch_admin_recursive;
DROP ROLE regress_plainrole;
+-- fail, can't drop regress_createrole yet, due to outstanding grants
+DROP ROLE regress_createrole;
+
-- ok, should be able to drop non-superuser roles we created
DROP ROLE regress_createdb;
-DROP ROLE regress_createrole;
DROP ROLE regress_login;
DROP ROLE regress_inherit;
DROP ROLE regress_connection_limit;
DROP ROLE regress_execute_server_program;
DROP ROLE regress_signal_backend;
+-- ok, dropped the other roles first so this is ok now
+DROP ROLE regress_createrole;
+
-- fail, role still owns database objects
DROP ROLE regress_tenant;
CREATE USER regress_priv_user10;
CREATE ROLE regress_priv_role;
+-- test GRANTED BY with DROP OWNED and REASSIGN OWNED
+GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION;
+GRANT regress_priv_user1 TO regress_priv_user3 GRANTED BY regress_priv_user2;
+DROP ROLE regress_priv_user2; -- fail, dependency
+REASSIGN OWNED BY regress_priv_user2 TO regress_priv_user4;
+DROP ROLE regress_priv_user2; -- still fail, REASSIGN OWNED doesn't help
+DROP OWNED BY regress_priv_user2;
+DROP ROLE regress_priv_user2; -- ok now, DROP OWNED does the job
+
+-- test that removing granted role or grantee role removes dependency
+GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION;
+GRANT regress_priv_user1 TO regress_priv_user4 GRANTED BY regress_priv_user3;
+DROP ROLE regress_priv_user3; -- should fail, dependency
+DROP ROLE regress_priv_user4; -- ok
+DROP ROLE regress_priv_user3; -- ok now
+GRANT regress_priv_user1 TO regress_priv_user5 WITH ADMIN OPTION;
+GRANT regress_priv_user1 TO regress_priv_user6 GRANTED BY regress_priv_user5;
+DROP ROLE regress_priv_user5; -- should fail, dependency
+DROP ROLE regress_priv_user1, regress_priv_user5; -- ok, despite order
+
+-- recreate the roles we just dropped
+CREATE USER regress_priv_user1;
+CREATE USER regress_priv_user2;
+CREATE USER regress_priv_user3;
+CREATE USER regress_priv_user4;
+CREATE USER regress_priv_user5;
+
GRANT pg_read_all_data TO regress_priv_user6;
GRANT pg_write_all_data TO regress_priv_user7;
GRANT pg_read_all_settings TO regress_priv_user8 WITH ADMIN OPTION;