From fb777b79bbae93eadd389507b853c64aa4cee606 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Tue, 29 Jun 2021 11:10:25 -0700
Subject: [PATCH v4 4/5] Add default role for database operations

Reducing the number of administrative tasks that require superuser
privileges by adding a new pg_database_security role and
delegating to it the authority to create, alter and drop most
database interal objects, though not if doing so would have network
or host security implications.
---
 doc/src/sgml/ref/alter_role.sgml              |   2 +
 doc/src/sgml/ref/create_role.sgml             |   3 +-
 src/backend/catalog/aclchk.c                  | 174 +++++++++++++-----
 src/backend/catalog/objectaddress.c           |   3 +-
 src/backend/catalog/pg_proc.c                 |  10 +-
 src/backend/commands/aggregatecmds.c          |   9 +-
 src/backend/commands/alter.c                  |  14 +-
 src/backend/commands/amcmds.c                 |   7 +-
 src/backend/commands/collationcmds.c          |   6 +-
 src/backend/commands/dbcommands.c             |  11 +-
 src/backend/commands/functioncmds.c           |  27 ++-
 src/backend/commands/opclasscmds.c            |  16 +-
 src/backend/commands/proclang.c               |   6 +-
 src/backend/commands/tablecmds.c              |   4 +-
 src/backend/commands/trigger.c                |   4 +-
 src/backend/commands/tsearchcmds.c            |  11 +-
 src/backend/commands/typecmds.c               |  11 +-
 src/backend/commands/user.c                   |  38 ++--
 src/include/catalog/pg_authid.dat             |   5 +
 src/test/regress/expected/alter_generic.out   |   2 +-
 .../regress/expected/create_function_3.out    |   4 +-
 src/test/regress/expected/privileges.out      | 102 +++++++++-
 src/test/regress/sql/privileges.sql           |  94 +++++++++-
 23 files changed, 452 insertions(+), 111 deletions(-)

diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index 5aa5648ae7..a6754cb500 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -70,6 +70,8 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
    <link linkend="sql-revoke"><command>REVOKE</command></link> for that.)
    Attributes not mentioned in the command retain their previous settings.
    Database superusers can change any of these settings for any role.
+   Users with membership in the <literal>pg_database_security</literal> role
+   may change the <literal>BYPASSRLS</literal> setting.
    Roles having <literal>CREATEROLE</literal> privilege can change any of these
    settings except <literal>SUPERUSER</literal>, <literal>REPLICATION</literal>,
    and <literal>BYPASSRLS</literal>; but only for non-superuser and
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 9468ff162c..ce1621d48b 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -195,7 +195,8 @@ in sync when changing the above synopsis!
        <para>
         These clauses determine whether a role bypasses every row-level
         security (RLS) policy.  <literal>NOBYPASSRLS</literal> is the default.
-        You must be a superuser to create a new role having
+        You must be a superuser or a member of role
+        <literal>pg_database_security</literal> to create a new role having
         the <literal>BYPASSRLS</literal> attribute.
        </para>
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 1316a6e36c..8c43a69c6e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3882,9 +3882,11 @@ pg_class_aclmask_ext(Oid table_oid, Oid roleid, AclMode mask,
 		mask &= ~(ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE);
 
 	/*
-	 * Otherwise, superusers bypass all permission-checking.
+	 * Otherwise, superusers and members of the pg_database_security role bypass all
+	 * permission-checking.
 	 */
-	if (superuser_arg(roleid))
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 	{
 		ReleaseSysCache(tuple);
 		return mask;
@@ -3963,8 +3965,12 @@ pg_database_aclmask(Oid db_oid, Oid roleid,
 	Acl		   *acl;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return mask;
 
 	/*
@@ -4017,8 +4023,12 @@ pg_proc_aclmask(Oid proc_oid, Oid roleid,
 	Acl		   *acl;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return mask;
 
 	/*
@@ -4071,8 +4081,12 @@ pg_language_aclmask(Oid lang_oid, Oid roleid,
 	Acl		   *acl;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return mask;
 
 	/*
@@ -4138,8 +4152,12 @@ pg_largeobject_aclmask_snapshot(Oid lobj_oid, Oid roleid,
 	Acl		   *acl;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return mask;
 
 	/*
@@ -4207,8 +4225,12 @@ pg_namespace_aclmask(Oid nsp_oid, Oid roleid,
 	Acl		   *acl;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return mask;
 
 	/*
@@ -4489,8 +4511,12 @@ pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how)
 
 	Form_pg_type typeForm;
 
-	/* Bypass permission checks for superusers */
-	if (superuser_arg(roleid))
+	/*
+	 * Bypass permission checks for superusers and members of the
+	 * pg_database_security role
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return mask;
 
 	/*
@@ -4834,8 +4860,12 @@ pg_class_ownercheck(Oid class_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(class_oid));
@@ -4860,8 +4890,12 @@ pg_type_ownercheck(Oid type_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
@@ -4886,8 +4920,12 @@ pg_oper_ownercheck(Oid oper_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(oper_oid));
@@ -4912,8 +4950,12 @@ pg_proc_ownercheck(Oid proc_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc_oid));
@@ -4938,8 +4980,12 @@ pg_language_ownercheck(Oid lan_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(lan_oid));
@@ -4970,8 +5016,12 @@ pg_largeobject_ownercheck(Oid lobj_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	/* There's no syscache for pg_largeobject_metadata */
@@ -5010,8 +5060,12 @@ pg_namespace_ownercheck(Oid nsp_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(nsp_oid));
@@ -5067,8 +5121,12 @@ pg_opclass_ownercheck(Oid opc_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opc_oid));
@@ -5094,8 +5152,12 @@ pg_opfamily_ownercheck(Oid opf_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opf_oid));
@@ -5121,8 +5183,12 @@ pg_ts_dict_ownercheck(Oid dict_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dict_oid));
@@ -5148,8 +5214,12 @@ pg_ts_config_ownercheck(Oid cfg_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfg_oid));
@@ -5265,8 +5335,12 @@ pg_database_ownercheck(Oid db_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			dba;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(db_oid));
@@ -5291,8 +5365,12 @@ pg_collation_ownercheck(Oid coll_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(COLLOID, ObjectIdGetDatum(coll_oid));
@@ -5317,8 +5395,12 @@ pg_conversion_ownercheck(Oid conv_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(CONVOID, ObjectIdGetDatum(conv_oid));
@@ -5445,8 +5527,12 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid)
 	HeapTuple	tuple;
 	Oid			ownerId;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers and members of the pg_database_security role bypass all
+	 * permission checking.
+	 */
+	if (superuser_arg(roleid) ||
+		has_privs_of_role(roleid, ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	tuple = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stat_oid));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..1a3c50ba56 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2564,7 +2564,8 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_ACCESS_METHOD:
 			/* We treat these object types as being owned by superusers */
-			if (!superuser_arg(roleid))
+			if (!superuser_arg(roleid) &&
+				!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 				ereport(ERROR,
 						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 						 errmsg("must be superuser")));
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 1454d2fb67..874f4025b1 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -21,6 +21,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_proc.h"
@@ -694,9 +695,16 @@ ProcedureCreate(const char *procedureName,
 			set_items = (ArrayType *) DatumGetPointer(proconfig);
 			if (set_items)		/* Need a new GUC nesting level */
 			{
+				GucContext	gc;
+
 				save_nestlevel = NewGUCNestLevel();
+				if (superuser() ||
+					has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
+					gc = PGC_SUSET;
+				else
+					gc = PGC_USERSET;
 				ProcessGUCArray(set_items,
-								(superuser() ? PGC_SUSET : PGC_USERSET),
+								gc,
 								PGC_S_SESSION,
 								GUC_ACTION_SAVE);
 			}
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 046cf2df08..f59ab758fd 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -25,6 +25,7 @@
 #include "access/htup_details.h"
 #include "catalog/dependency.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
@@ -342,7 +343,9 @@ DefineAggregate(ParseState *pstate,
 	if (transTypeType == TYPTYPE_PSEUDO &&
 		!IsPolymorphicType(transTypeId))
 	{
-		if (transTypeId == INTERNALOID && superuser())
+		if (transTypeId == INTERNALOID &&
+			(superuser() ||
+			 has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY)))
 			 /* okay */ ;
 		else
 			ereport(ERROR,
@@ -383,7 +386,9 @@ DefineAggregate(ParseState *pstate,
 		if (mtransTypeType == TYPTYPE_PSEUDO &&
 			!IsPolymorphicType(mtransTypeId))
 		{
-			if (mtransTypeId == INTERNALOID && superuser())
+			if (mtransTypeId == INTERNALOID &&
+				(superuser() ||
+				 has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY)))
 				 /* okay */ ;
 			else
 				ereport(ERROR,
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..01cbe45489 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -22,6 +22,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_event_trigger.h"
@@ -206,13 +207,14 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 		namespaceId = InvalidOid;
 
 	/* Permission checks ... superusers can always do it */
-	if (!superuser())
+	if (!superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 	{
 		/* Fail if object does not have an explicit owner */
 		if (Anum_owner <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to rename %s",
+					 errmsg("must be superuser or pg_database_security to rename %s",
 							getObjectDescriptionOids(classId, objectId))));
 
 		/* Otherwise, must be owner of the existing object */
@@ -731,7 +733,8 @@ AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid)
 	CheckSetNamespace(oldNspOid, nspOid);
 
 	/* Permission checks ... superusers can always do it */
-	if (!superuser())
+	if (!superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 	{
 		Datum		owner;
 		Oid			ownerId;
@@ -741,7 +744,7 @@ AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid)
 		if (Anum_owner <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to set schema of %s",
+					 errmsg("must be superuser or pg_database_security to set schema of %s",
 							getObjectDescriptionOids(classId, objid))));
 
 		/* Otherwise, must be owner of the existing object */
@@ -972,7 +975,8 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, Oid new_ownerId)
 		bool	   *replaces;
 
 		/* Superusers can bypass permission checks */
-		if (!superuser())
+		if (!superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		{
 			/* must be owner */
 			if (!has_privs_of_role(GetUserId(), old_ownerId))
diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index 188109e474..4896fc0193 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -20,11 +20,13 @@
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
+#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
@@ -54,12 +56,13 @@ CreateAccessMethod(CreateAmStmt *stmt)
 	rel = table_open(AccessMethodRelationId, RowExclusiveLock);
 
 	/* Must be super user */
-	if (!superuser())
+	if (!superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied to create access method \"%s\"",
 						stmt->amname),
-				 errhint("Must be superuser to create an access method.")));
+				 errhint("Must be superuser or pg_database_security to create an access method.")));
 
 	/* Check if name is used */
 	amoid = GetSysCacheOid1(AMNAME, Anum_pg_am_oid,
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index ebb0994db3..e487e75b0e 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -21,6 +21,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_collation.h"
 #include "commands/alter.h"
 #include "commands/collationcmds.h"
@@ -506,10 +507,11 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
 	Oid			nspid = PG_GETARG_OID(0);
 	int			ncreated = 0;
 
-	if (!superuser())
+	if (!superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("must be superuser to import system collations")));
+				 errmsg("must be superuser or pg_database_security to import system collations")));
 
 	if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(nspid)))
 		ereport(ERROR,
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 2b159b60eb..9b1a38ffd6 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -763,7 +763,9 @@ check_encoding_locale_matches(int encoding, const char *collate, const char *cty
 #ifdef WIN32
 		  encoding == PG_UTF8 ||
 #endif
-		  (encoding == PG_SQL_ASCII && superuser())))
+		  (encoding == PG_SQL_ASCII &&
+		   (superuser() ||
+			has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY)))))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("encoding \"%s\" does not match locale \"%s\"",
@@ -778,7 +780,9 @@ check_encoding_locale_matches(int encoding, const char *collate, const char *cty
 #ifdef WIN32
 		  encoding == PG_UTF8 ||
 #endif
-		  (encoding == PG_SQL_ASCII && superuser())))
+		  (encoding == PG_SQL_ASCII &&
+		   (superuser() ||
+			has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY)))))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("encoding \"%s\" does not match locale \"%s\"",
@@ -1923,7 +1927,8 @@ have_createdb_privilege(void)
 	HeapTuple	utup;
 
 	/* Superusers can always do everything */
-	if (superuser())
+	if (superuser() ||
+		has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		return true;
 
 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId()));
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 736d04780a..ac39ced1c7 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -41,6 +41,7 @@
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
@@ -712,10 +713,11 @@ interpret_func_support(DefElem *defel)
 	 * you be superuser to specify a support function, so privilege on the
 	 * support function is moot.
 	 */
-	if (!superuser())
+	if (!superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("must be superuser to specify a support function")));
+				 errmsg("must be superuser or pg_database_security to specify a support function")));
 
 	return procOid;
 }
@@ -1143,7 +1145,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 	else
 	{
 		/* if untrusted language, must be superuser */
-		if (!superuser())
+		if (!superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 			aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_LANGUAGE,
 						   NameStr(languageStruct->lanname));
 	}
@@ -1157,10 +1160,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 	 * leakproof functions can see tuples which have not yet been filtered out
 	 * by security barrier views or row-level security policies.
 	 */
-	if (isLeakProof && !superuser())
+	if (isLeakProof && !superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("only superuser can define a leakproof function")));
+				 errmsg("only superuser or pg_database_security can define a leakproof function")));
 
 	if (transformDefElem)
 	{
@@ -1441,10 +1445,11 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
 	if (leakproof_item)
 	{
 		procForm->proleakproof = intVal(leakproof_item->arg);
-		if (procForm->proleakproof && !superuser())
+		if (procForm->proleakproof && !superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("only superuser can define a leakproof function")));
+					 errmsg("only superuser or pg_database_security can define a leakproof function")));
 	}
 	if (cost_item)
 	{
@@ -1685,10 +1690,11 @@ CreateCast(CreateCastStmt *stmt)
 		 * Must be superuser to create binary-compatible casts, since
 		 * erroneous casts can easily crash the backend.
 		 */
-		if (!superuser())
+		if (!superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to create a cast WITHOUT FUNCTION")));
+					 errmsg("must be superuser or pg_database_security to create a cast WITHOUT FUNCTION")));
 
 		/*
 		 * Also, insist that the types match as to size, alignment, and
@@ -2148,7 +2154,8 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic)
 	else
 	{
 		/* if untrusted language, must be superuser */
-		if (!superuser())
+		if (!superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 			aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_LANGUAGE,
 						   NameStr(languageStruct->lanname));
 	}
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index fad39e2b75..d3f7412ba2 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
@@ -405,10 +406,11 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	 *
 	 * XXX re-enable NOT_USED code sections below if you remove this test.
 	 */
-	if (!superuser())
+	if (!superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("must be superuser to create an operator class")));
+				 errmsg("must be superuser or pg_database_security to create an operator class")));
 
 	/* Look up the datatype */
 	typeoid = typenameTypeId(NULL, stmt->datatype);
@@ -786,10 +788,11 @@ DefineOpFamily(CreateOpFamilyStmt *stmt)
 	 * Currently, we require superuser privileges to create an opfamily. See
 	 * comments in DefineOpClass.
 	 */
-	if (!superuser())
+	if (!superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("must be superuser to create an operator family")));
+				 errmsg("must be superuser or pg_database_security to create an operator family")));
 
 	/* Insert pg_opfamily catalog entry */
 	return CreateOpFamily(stmt->amname, opfname, namespaceoid, amoid);
@@ -846,10 +849,11 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
 	 *
 	 * XXX re-enable NOT_USED code sections below if you remove this test.
 	 */
-	if (!superuser())
+	if (!superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("must be superuser to alter an operator family")));
+				 errmsg("must be superuser or pg_database_security to alter an operator family")));
 
 	/*
 	 * ADD and DROP cases need separate code from here on down.
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index 81598d3e08..1d40425deb 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -18,6 +18,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_proc.h"
@@ -62,10 +63,11 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 	/*
 	 * Check permission
 	 */
-	if (!superuser())
+	if (!superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("must be superuser to create custom procedural language")));
+				 errmsg("must be superuser or pg_database_security to create custom procedural language")));
 
 	/*
 	 * Lookup the PL handler function and check that it is of the expected
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 97a9725df7..308829fdf8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -33,6 +33,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/partition.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
@@ -13227,7 +13228,8 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 		if (!recursing)
 		{
 			/* Superusers can always do it */
-			if (!superuser())
+			if (!superuser() &&
+				!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 			{
 				Oid			namespaceOid = tuple_class->relnamespace;
 				AclResult	aclresult;
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 07c73f39de..7a46dcdd75 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -26,6 +26,7 @@
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/partition.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_proc.h"
@@ -1574,7 +1575,8 @@ EnableDisableTrigger(Relation rel, const char *tgname,
 			/* system trigger ... ok to process? */
 			if (skip_system)
 				continue;
-			if (!superuser())
+			if (!superuser() &&
+				!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 				ereport(ERROR,
 						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 						 errmsg("permission denied: \"%s\" is a system trigger",
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index e06fb32b3d..c94e651fc9 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -25,6 +25,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_ts_config.h"
@@ -188,10 +189,11 @@ DefineTSParser(List *names, List *parameters)
 	Oid			namespaceoid;
 	ObjectAddress address;
 
-	if (!superuser())
+	if (!superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("must be superuser to create text search parsers")));
+				 errmsg("must be superuser or pg_database_security to create text search parsers")));
 
 	prsRel = table_open(TSParserRelationId, RowExclusiveLock);
 
@@ -695,10 +697,11 @@ DefineTSTemplate(List *names, List *parameters)
 	char	   *tmplname;
 	ObjectAddress address;
 
-	if (!superuser())
+	if (!superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("must be superuser to create text search templates")));
+				 errmsg("must be superuser or pg_database_security to create text search templates")));
 
 	/* Convert list of names to a name and namespace */
 	namespaceoid = QualifiedNameGetCreationNamespace(names, &tmplname);
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 58ec65c6af..3c89214fe5 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -211,10 +211,11 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 	 *
 	 * XXX re-enable NOT_USED code sections below if you remove this test.
 	 */
-	if (!superuser())
+	if (!superuser() &&
+		!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("must be superuser to create a base type")));
+				 errmsg("must be superuser or pg_database_security to create a base type")));
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(names, &typeName);
@@ -3756,7 +3757,8 @@ AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype)
 	if (typTup->typowner != newOwnerId)
 	{
 		/* Superusers can always do it */
-		if (!superuser())
+		if (!superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 		{
 			/* Otherwise, must be owner of the existing object */
 			if (!pg_type_ownercheck(typTup->oid, GetUserId()))
@@ -4288,7 +4290,8 @@ AlterType(AlterTypeStmt *stmt)
 	 */
 	if (requireSuper)
 	{
-		if (!superuser())
+		if (!superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("must be superuser to alter a type")));
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 65bb733958..50b60b6bef 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -295,17 +295,19 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	}
 	else if (isreplication)
 	{
-		if (!superuser())
+		if (!superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to create replication users")));
+					 errmsg("must be superuser or member of role pg_database_security to create replication users")));
 	}
 	else if (bypassrls)
 	{
-		if (!superuser())
+		if (!superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to create bypassrls users")));
+					 errmsg("must be superuser or member of role pg_database_security to create bypassrls users")));
 	}
 	else
 	{
@@ -709,10 +711,10 @@ AlterRole(AlterRoleStmt *stmt)
 	roleid = authform->oid;
 
 	/*
-	 * To mess with a superuser or replication role in any way you gotta be
-	 * superuser.  We also insist on superuser to change the BYPASSRLS
-	 * property.  Otherwise, if you don't have createrole, you're only allowed
-	 * to change your own password.
+	 * To mess with a superuser in any way you gotta be superuser.  We also
+	 * insist on superuser or pg_database_security to change the REPLICATION
+	 * and BYPASSRLS properties.  Otherwise, if you don't have createrole,
+	 * you're only allowed to change your own password.
 	 */
 	if (authform->rolsuper || issuper >= 0)
 	{
@@ -723,17 +725,19 @@ AlterRole(AlterRoleStmt *stmt)
 	}
 	else if (authform->rolreplication || isreplication >= 0)
 	{
-		if (!superuser())
+		if (!superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("must be superuser to alter replication roles or change replication attribute")));
 	}
 	else if (bypassrls >= 0)
 	{
-		if (!superuser())
+		if (!superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to change bypassrls attribute")));
+					 errmsg("must be superuser or member of role pg_database_security to change bypassrls attribute")));
 	}
 	else if (!have_createrole_privilege())
 	{
@@ -976,11 +980,12 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
 
 	if (!stmt->role && !stmt->database)
 	{
-		/* Must be superuser to alter settings globally. */
-		if (!superuser())
+		/* Must be superuser or pg_database_security to alter settings globally. */
+		if (!superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to alter settings globally")));
+					 errmsg("must be superuser or member of role pg_database_security to alter settings globally")));
 	}
 
 	AlterSetting(databaseid, roleid, stmt->setstmt);
@@ -1254,10 +1259,11 @@ RenameRole(const char *oldname, const char *newname)
 	 */
 	if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
 	{
-		if (!superuser())
+		if (!superuser() &&
+			!has_privs_of_role(GetUserId(), ROLE_PG_DATABASE_SECURITY))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to rename superusers")));
+					 errmsg("must be superuser or member of role pg_database_security to rename superusers")));
 	}
 	else
 	{
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 6fff059699..7c9e407b41 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -94,5 +94,10 @@
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '9279', oid_symbol => 'ROLE_PG_DATABASE_SECURITY',
+  rolname => 'pg_database_security', rolsuper => 'f', rolinherit => 't',
+  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolreplication => 'f', rolbypassrls => 't', rolconnlimit => '-1',
+  rolpassword => '_null_', rolvaliduntil => '_null_' },
 
 ]
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index 505eb7ede5..64e1ab571b 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -366,7 +366,7 @@ CREATE ROLE regress_alter_generic_user5 NOSUPERUSER;
 CREATE OPERATOR FAMILY alt_opf5 USING btree;
 SET ROLE regress_alter_generic_user5;
 ALTER OPERATOR FAMILY alt_opf5 USING btree ADD OPERATOR 1 < (int4, int2), FUNCTION 1 btint42cmp(int4, int2);
-ERROR:  must be superuser to alter an operator family
+ERROR:  must be superuser or pg_database_security to alter an operator family
 RESET ROLE;
 ERROR:  current transaction is aborted, commands ignored until end of transaction block
 DROP OPERATOR FAMILY alt_opf5 USING btree;
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index 5955859bb5..33a5b689c7 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -166,10 +166,10 @@ SET SESSION AUTHORIZATION regress_unpriv_user;
 SET search_path TO temp_func_test, public;
 ALTER FUNCTION functest_E_1(int) NOT LEAKPROOF;
 ALTER FUNCTION functest_E_2(int) LEAKPROOF;
-ERROR:  only superuser can define a leakproof function
+ERROR:  only superuser or pg_database_security can define a leakproof function
 CREATE FUNCTION functest_E_3(int) RETURNS bool LANGUAGE 'sql'
        LEAKPROOF AS 'SELECT $1 < 200';	-- fail
-ERROR:  only superuser can define a leakproof function
+ERROR:  only superuser or pg_database_security can define a leakproof function
 RESET SESSION AUTHORIZATION;
 --
 -- CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index adb6813194..6fc395489b 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -15,6 +15,7 @@ DROP ROLE IF EXISTS regress_priv_user6;
 DROP ROLE IF EXISTS regress_priv_user7;
 DROP ROLE IF EXISTS regress_priv_user8;
 DROP ROLE IF EXISTS regress_priv_user9;
+DROP ROLE IF EXISTS regress_priv_user10;
 SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid;
  lo_unlink 
 -----------
@@ -33,6 +34,7 @@ CREATE USER regress_priv_user6;
 CREATE USER regress_priv_user7;
 CREATE USER regress_priv_user8;
 CREATE USER regress_priv_user9;
+CREATE USER regress_priv_user10;
 GRANT pg_read_all_data TO regress_priv_user6;
 GRANT pg_write_all_data TO regress_priv_user7;
 CREATE GROUP regress_priv_group1;
@@ -1862,6 +1864,101 @@ COMMENT ON SERVER user9_server IS 'test comment';
 DROP SERVER user9_server;
 DROP FOREIGN DATA WRAPPER user9_fdw;
 RESET SESSION AUTHORIZATION;
+-- test pg_database_security authority over numerous database objects
+GRANT pg_database_security TO regress_priv_user10;
+SET SESSION AUTHORIZATION regress_priv_user10;
+CREATE ACCESS METHOD user10_am TYPE INDEX HANDLER gisthandler;
+CREATE CAST (text AS json) WITHOUT FUNCTION;
+CREATE DATABASE user10_db;
+GRANT ALL ON DATABASE user10_db TO regress_priv_user10;
+CREATE FUNCTION user10_event_trigger_func() RETURNS event_trigger AS $$
+BEGIN
+    RAISE DEBUG 'user10_event_trigger: % %', tg_event, tg_tag;
+END
+$$ LANGUAGE PLPGSQL;
+CREATE EVENT TRIGGER user10_event_trigger ON ddl_command_start -- no
+   EXECUTE PROCEDURE user10_event_trigger_func();
+ERROR:  permission denied to create event trigger "user10_event_trigger"
+HINT:  Must be superuser or a member of the pg_host_security role to create an event trigger.
+CREATE FOREIGN DATA WRAPPER user10_fdw VALIDATOR postgresql_fdw_validator; -- no
+ERROR:  permission denied to create foreign-data wrapper "user10_fdw"
+HINT:  Must be superuser or member of pg_network_security to create a foreign-data wrapper.
+CREATE FUNCTION user10_secdef_func(boolean) RETURNS text
+  AS 'select $1::text;'
+  LANGUAGE sql SECURITY DEFINER;
+CREATE INDEX user10_index ON pg_catalog.pg_class(oid); -- no
+ERROR:  permission denied: "pg_class" is a system catalog
+CREATE LANGUAGE user10_lang HANDLER plpgsql_call_handler;
+CREATE OPERATOR CLASS user10_ops
+    FOR TYPE box USING gist AS
+    OPERATOR 1  <<,
+    OPERATOR 2  &<,
+    OPERATOR 3  &&,
+    OPERATOR 4  &>,
+    OPERATOR 5  >>,
+    OPERATOR 6  ~=,
+    OPERATOR 7  @>,
+    OPERATOR 8  <@,
+    OPERATOR 9  &<|,
+    OPERATOR 10 <<|,
+    OPERATOR 11 |>>,
+    OPERATOR 12 |&>,
+    FUNCTION 1  gist_box_consistent(internal, box, smallint, oid, internal),
+    FUNCTION 2  gist_box_union(internal, internal),
+    FUNCTION 5  gist_box_penalty(internal, internal, internal),
+    FUNCTION 6  gist_box_picksplit(internal, internal),
+    FUNCTION 7  gist_box_same(box, box, internal);
+CREATE TABLE user10_tbl (id integer, str text);
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION user10_publication FOR TABLE user10_tbl;
+RESET client_min_messages;
+CREATE RULE user10_rule_ins AS ON INSERT TO pg_catalog.pg_class -- no
+  DO INSTEAD DELETE FROM pg_catalog.pg_am;
+ERROR:  permission denied: "pg_class" is a system catalog
+CREATE SCHEMA user10_schema;
+SET client_min_messages = 'ERROR';
+CREATE SUBSCRIPTION user10_subscription CONNECTION 'dbname=nosuchdb' PUBLICATION nosuchpub WITH (connect = false, enabled = false, slot_name = NONE, create_slot = false); -- no
+ERROR:  must be superuser or a member of the pg_logical_replication role to create subscriptions
+RESET client_min_messages;
+CREATE STATISTICS user10_stats ON id, str FROM user10_tbl;
+CREATE TEXT SEARCH CONFIGURATION user10_tsconfig (COPY=english);
+CREATE TEXT SEARCH DICTIONARY user10_tsdict (Template=ispell, DictFile=ispell_sample, AffFile=ispell_sample);
+CREATE TEXT SEARCH PARSER user10_tsparser
+    (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype);
+CREATE TEXT SEARCH TEMPLATE user10_tstemplate (lexize=dsimple_lexize);
+CREATE TRANSFORM FOR bigint LANGUAGE SQL (
+    FROM SQL WITH FUNCTION prsd_lextype(internal),
+    TO SQL WITH FUNCTION int8recv(internal));
+CREATE FUNCTION user10_trigger_func() RETURNS trigger AS $$
+BEGIN
+    RAISE DEBUG 'user10_trigger';
+END
+$$ LANGUAGE PLPGSQL;
+CREATE TRIGGER user10_trigger
+    BEFORE INSERT OR UPDATE ON user10_tbl
+    FOR EACH ROW
+    EXECUTE PROCEDURE user10_trigger_func();
+CREATE TYPE user10_type;
+CREATE VIEW user10_view AS SELECT * FROM user10_tbl;
+DROP ACCESS METHOD user10_am;
+DROP CAST (text AS json);
+REVOKE ALL ON DATABASE user10_db FROM regress_priv_user10;
+DROP DATABASE user10_db;
+DROP FUNCTION user10_event_trigger_func();
+DROP FUNCTION user10_secdef_func(boolean);
+DROP LANGUAGE user10_lang;
+DROP OPERATOR CLASS user10_ops USING gist;
+DROP OPERATOR FAMILY user10_ops USING gist;
+DROP PUBLICATION user10_publication;
+DROP SCHEMA user10_schema;
+DROP STATISTICS user10_stats;
+DROP TEXT SEARCH CONFIGURATION user10_tsconfig;
+DROP TEXT SEARCH DICTIONARY user10_tsdict;
+DROP TRIGGER user10_trigger ON user10_tbl;
+DROP FUNCTION user10_trigger_func();
+DROP TYPE user10_type;
+DROP VIEW user10_view;
+DROP TABLE user10_tbl;
 -- test default ACLs
 \c -
 CREATE SCHEMA testns;
@@ -2363,8 +2460,9 @@ DROP USER regress_priv_user6;
 DROP USER regress_priv_user7;
 DROP USER regress_priv_user8;
 DROP USER regress_priv_user9;
-DROP USER regress_priv_user10; -- does not exist
-ERROR:  role "regress_priv_user10" does not exist
+DROP USER regress_priv_user10;
+DROP USER regress_priv_user11; -- does not exist
+ERROR:  role "regress_priv_user11" does not exist
 -- permissions with LOCK TABLE
 CREATE USER regress_locktable_user;
 CREATE TABLE lock_table (a int);
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 3bef0b6b80..bda5350ab0 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -19,6 +19,7 @@ DROP ROLE IF EXISTS regress_priv_user6;
 DROP ROLE IF EXISTS regress_priv_user7;
 DROP ROLE IF EXISTS regress_priv_user8;
 DROP ROLE IF EXISTS regress_priv_user9;
+DROP ROLE IF EXISTS regress_priv_user10;
 
 SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid;
 
@@ -36,6 +37,7 @@ CREATE USER regress_priv_user6;
 CREATE USER regress_priv_user7;
 CREATE USER regress_priv_user8;
 CREATE USER regress_priv_user9;
+CREATE USER regress_priv_user10;
 
 GRANT pg_read_all_data TO regress_priv_user6;
 GRANT pg_write_all_data TO regress_priv_user7;
@@ -1127,6 +1129,95 @@ DROP SERVER user9_server;
 DROP FOREIGN DATA WRAPPER user9_fdw;
 RESET SESSION AUTHORIZATION;
 
+-- test pg_database_security authority over numerous database objects
+GRANT pg_database_security TO regress_priv_user10;
+SET SESSION AUTHORIZATION regress_priv_user10;
+CREATE ACCESS METHOD user10_am TYPE INDEX HANDLER gisthandler;
+CREATE CAST (text AS json) WITHOUT FUNCTION;
+CREATE DATABASE user10_db;
+GRANT ALL ON DATABASE user10_db TO regress_priv_user10;
+CREATE FUNCTION user10_event_trigger_func() RETURNS event_trigger AS $$
+BEGIN
+    RAISE DEBUG 'user10_event_trigger: % %', tg_event, tg_tag;
+END
+$$ LANGUAGE PLPGSQL;
+CREATE EVENT TRIGGER user10_event_trigger ON ddl_command_start -- no
+   EXECUTE PROCEDURE user10_event_trigger_func();
+CREATE FOREIGN DATA WRAPPER user10_fdw VALIDATOR postgresql_fdw_validator; -- no
+CREATE FUNCTION user10_secdef_func(boolean) RETURNS text
+  AS 'select $1::text;'
+  LANGUAGE sql SECURITY DEFINER;
+CREATE INDEX user10_index ON pg_catalog.pg_class(oid); -- no
+CREATE LANGUAGE user10_lang HANDLER plpgsql_call_handler;
+CREATE OPERATOR CLASS user10_ops
+    FOR TYPE box USING gist AS
+    OPERATOR 1  <<,
+    OPERATOR 2  &<,
+    OPERATOR 3  &&,
+    OPERATOR 4  &>,
+    OPERATOR 5  >>,
+    OPERATOR 6  ~=,
+    OPERATOR 7  @>,
+    OPERATOR 8  <@,
+    OPERATOR 9  &<|,
+    OPERATOR 10 <<|,
+    OPERATOR 11 |>>,
+    OPERATOR 12 |&>,
+    FUNCTION 1  gist_box_consistent(internal, box, smallint, oid, internal),
+    FUNCTION 2  gist_box_union(internal, internal),
+    FUNCTION 5  gist_box_penalty(internal, internal, internal),
+    FUNCTION 6  gist_box_picksplit(internal, internal),
+    FUNCTION 7  gist_box_same(box, box, internal);
+CREATE TABLE user10_tbl (id integer, str text);
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION user10_publication FOR TABLE user10_tbl;
+RESET client_min_messages;
+CREATE RULE user10_rule_ins AS ON INSERT TO pg_catalog.pg_class -- no
+  DO INSTEAD DELETE FROM pg_catalog.pg_am;
+CREATE SCHEMA user10_schema;
+SET client_min_messages = 'ERROR';
+CREATE SUBSCRIPTION user10_subscription CONNECTION 'dbname=nosuchdb' PUBLICATION nosuchpub WITH (connect = false, enabled = false, slot_name = NONE, create_slot = false); -- no
+RESET client_min_messages;
+CREATE STATISTICS user10_stats ON id, str FROM user10_tbl;
+CREATE TEXT SEARCH CONFIGURATION user10_tsconfig (COPY=english);
+CREATE TEXT SEARCH DICTIONARY user10_tsdict (Template=ispell, DictFile=ispell_sample, AffFile=ispell_sample);
+CREATE TEXT SEARCH PARSER user10_tsparser
+    (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype);
+CREATE TEXT SEARCH TEMPLATE user10_tstemplate (lexize=dsimple_lexize);
+CREATE TRANSFORM FOR bigint LANGUAGE SQL (
+    FROM SQL WITH FUNCTION prsd_lextype(internal),
+    TO SQL WITH FUNCTION int8recv(internal));
+CREATE FUNCTION user10_trigger_func() RETURNS trigger AS $$
+BEGIN
+    RAISE DEBUG 'user10_trigger';
+END
+$$ LANGUAGE PLPGSQL;
+CREATE TRIGGER user10_trigger
+    BEFORE INSERT OR UPDATE ON user10_tbl
+    FOR EACH ROW
+    EXECUTE PROCEDURE user10_trigger_func();
+CREATE TYPE user10_type;
+CREATE VIEW user10_view AS SELECT * FROM user10_tbl;
+DROP ACCESS METHOD user10_am;
+DROP CAST (text AS json);
+REVOKE ALL ON DATABASE user10_db FROM regress_priv_user10;
+DROP DATABASE user10_db;
+DROP FUNCTION user10_event_trigger_func();
+DROP FUNCTION user10_secdef_func(boolean);
+DROP LANGUAGE user10_lang;
+DROP OPERATOR CLASS user10_ops USING gist;
+DROP OPERATOR FAMILY user10_ops USING gist;
+DROP PUBLICATION user10_publication;
+DROP SCHEMA user10_schema;
+DROP STATISTICS user10_stats;
+DROP TEXT SEARCH CONFIGURATION user10_tsconfig;
+DROP TEXT SEARCH DICTIONARY user10_tsdict;
+DROP TRIGGER user10_trigger ON user10_tbl;
+DROP FUNCTION user10_trigger_func();
+DROP TYPE user10_type;
+DROP VIEW user10_view;
+DROP TABLE user10_tbl;
+
 -- test default ACLs
 \c -
 
@@ -1428,7 +1519,8 @@ DROP USER regress_priv_user6;
 DROP USER regress_priv_user7;
 DROP USER regress_priv_user8;
 DROP USER regress_priv_user9;
-DROP USER regress_priv_user10; -- does not exist
+DROP USER regress_priv_user10;
+DROP USER regress_priv_user11; -- does not exist
 
 
 -- permissions with LOCK TABLE
-- 
2.21.1 (Apple Git-122.3)

