Role Attribute Bitmask Catalog Representation

Started by Adam Brightwellabout 11 years ago40 messages
#1Adam Brightwell
adam.brightwell@crunchydatasolutions.com
1 attachment(s)

All,

I am simply breaking this out into its own thread from the discussion on
additional role attributes (
/messages/by-id/20141015052259.GG28859@tamriel.snowman.net
).

A few related threads/discussions/posts:

*
/messages/by-id/20141016115914.GQ28859@tamriel.snowman.net
*
/messages/by-id/CA+TgmobkYXNOWKEKzX2qGPSr_nvacFGueV=orxND-xmZvOVYvg@mail.gmail.com
*
/messages/by-id/20141016115914.GQ28859@tamriel.snowman.net

Based on these above I have attached an initial WIP patch for review and
discussion that takes a swing at changing the catalog representation.

This patch includes:

* int64 (C) to int8 (SQL) mapping for genbki.
* replace all role attributes columns in pg_authid with single int64 column
named rolattr.
* update CreateRole and AlterRole to use rolattr.
* update all has_*_privilege functions to check rolattr.
* builtin SQL function 'has_role_attribute' that takes a role oid and text
name of the attribute as input and returns a boolean.

Items not currently addressed:

* New syntax - previous discussion indicated a potential desire for this,
but I feel more discussion needs to occur around these before proposing as
part of a patch. Specifically, how would CREATE USER/ROLE be affected? I
suppose it is OK to keep it as WITH <attribute_or_capability>, though if
ALTER ROLE is modified to have ADD | DROP CAPABILITY for consistency would
WITH CAPABILITY <value,...>, make more sense for CREATE? I also felt these
were mutually exclusive from an implementation perspective and therefore
thought it would be best to keep them separate.
* Documentation - want to gain feedback on implementation prior to making
changes.
* Update regression tests, rules test for system_views - want to gain
feedback on approach to handling pg_roles, etc. before updating.

Thanks,
Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

Attachments:

role-attribute-bitmask-v1.patchtext/x-patch; charset=US-ASCII; name=role-attribute-bitmask-v1.patchDownload
diff --git a/src/backend/catalog/Catalog.pm b/src/backend/catalog/Catalog.pm
new file mode 100644
index eb91c53..523b379
*** a/src/backend/catalog/Catalog.pm
--- b/src/backend/catalog/Catalog.pm
*************** sub Catalogs
*** 33,38 ****
--- 33,39 ----
  	my %RENAME_ATTTYPE = (
  		'int16'         => 'int2',
  		'int32'         => 'int4',
+ 		'int64'         => 'int8',
  		'Oid'           => 'oid',
  		'NameData'      => 'name',
  		'TransactionId' => 'xid');
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
new file mode 100644
index d30612c..93eb2e6
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
*************** aclcheck_error_type(AclResult aclerr, Oi
*** 3423,3448 ****
  }
  
  
- /* Check if given user has rolcatupdate privilege according to pg_authid */
- static bool
- has_rolcatupdate(Oid roleid)
- {
- 	bool		rolcatupdate;
- 	HeapTuple	tuple;
- 
- 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (!HeapTupleIsValid(tuple))
- 		ereport(ERROR,
- 				(errcode(ERRCODE_UNDEFINED_OBJECT),
- 				 errmsg("role with OID %u does not exist", roleid)));
- 
- 	rolcatupdate = ((Form_pg_authid) GETSTRUCT(tuple))->rolcatupdate;
- 
- 	ReleaseSysCache(tuple);
- 
- 	return rolcatupdate;
- }
- 
  /*
   * Relay for the various pg_*_mask routines depending on object kind
   */
--- 3423,3428 ----
*************** pg_class_aclmask(Oid table_oid, Oid role
*** 3630,3636 ****
  	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
  		IsSystemClass(table_oid, classForm) &&
  		classForm->relkind != RELKIND_VIEW &&
! 		!has_rolcatupdate(roleid) &&
  		!allowSystemTableMods)
  	{
  #ifdef ACLDEBUG
--- 3610,3616 ----
  	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
  		IsSystemClass(table_oid, classForm) &&
  		classForm->relkind != RELKIND_VIEW &&
! 		!role_has_attribute(roleid, ROLE_ATTR_CATUPDATE) &&
  		!allowSystemTableMods)
  	{
  #ifdef ACLDEBUG
*************** pg_extension_ownercheck(Oid ext_oid, Oid
*** 5051,5056 ****
--- 5031,5058 ----
  }
  
  /*
+  * Check whether the specified role has a specific role attribute.
+  */
+ bool
+ role_has_attribute(Oid roleid, RoleAttr attribute)
+ {
+ 	RoleAttr	attributes;
+ 	HeapTuple	tuple;
+ 
+ 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
+ 
+ 	if (!HeapTupleIsValid(tuple))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("role with OID %u does not exist", roleid)));
+ 
+ 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
+ 	ReleaseSysCache(tuple);
+ 
+ 	return ((attributes & attribute) > 0);
+ }
+ 
+ /*
   * Check whether specified role has CREATEROLE privilege (or is a superuser)
   *
   * Note: roles do not have owners per se; instead we use this test in
*************** pg_extension_ownercheck(Oid ext_oid, Oid
*** 5064,5102 ****
  bool
  has_createrole_privilege(Oid roleid)
  {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
  	/* Superusers bypass all permission checking. */
  	if (superuser_arg(roleid))
  		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  bool
  has_bypassrls_privilege(Oid roleid)
  {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
  	/* Superusers bypass all permission checking. */
  	if (superuser_arg(roleid))
  		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolbypassrls;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  /*
--- 5066,5089 ----
  bool
  has_createrole_privilege(Oid roleid)
  {
  	/* Superusers bypass all permission checking. */
  	if (superuser_arg(roleid))
  		return true;
  
! 	return role_has_attribute(roleid, ROLE_ATTR_CREATEROLE);
  }
  
+ /*
+  * Check whether specified role has BYPASSRLS privilege.
+  */
  bool
  has_bypassrls_privilege(Oid roleid)
  {
  	/* Superusers bypass all permission checking. */
  	if (superuser_arg(roleid))
  		return true;
  
! 	return role_has_attribute(roleid, ROLE_ATTR_BYPASSRLS);
  }
  
  /*
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
new file mode 100644
index a036c62..6904716
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
*************** CREATE VIEW user_mapping_options AS
*** 2884,2890 ****
             CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
             CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                         OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
!                        OR (SELECT rolsuper FROM pg_authid WHERE rolname = current_user) THEN (pg_options_to_table(um.umoptions)).option_value
                       ELSE NULL END AS character_data) AS option_value
      FROM _pg_user_mappings um;
  
--- 2884,2895 ----
             CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
             CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                         OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
!                        OR (
!                             SELECT has_role_attribute(pg_authid.oid, 'SUPERUSER') AS rolsuper
!                             FROM pg_authid
!                             WHERE rolname = current_user
!                           )
!                        THEN (pg_options_to_table(um.umoptions)).option_value
                       ELSE NULL END AS character_data) AS option_value
      FROM _pg_user_mappings um;
  
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
new file mode 100644
index a819952..8e02116
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 9,25 ****
  CREATE VIEW pg_roles AS
      SELECT
          rolname,
!         rolsuper,
!         rolinherit,
!         rolcreaterole,
!         rolcreatedb,
!         rolcatupdate,
!         rolcanlogin,
!         rolreplication,
          rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
-         rolbypassrls,
          setconfig as rolconfig,
          pg_authid.oid
      FROM pg_authid LEFT JOIN pg_db_role_setting s
--- 9,25 ----
  CREATE VIEW pg_roles AS
      SELECT
          rolname,
!         has_role_attribute(pg_authid.oid, 'SUPERUSER') AS rolsuper,
!         has_role_attribute(pg_authid.oid, 'INHERIT') AS rolinherit,
!         has_role_attribute(pg_authid.oid, 'CREATEROLE') AS rolcreaterole,
!         has_role_attribute(pg_authid.oid, 'CREATEDB') AS rolcreatedb,
!         has_role_attribute(pg_authid.oid, 'CATUPDATE') AS rolcatupdate,
!         has_role_attribute(pg_authid.oid, 'CANLOGIN') AS rolcanlogin,
!         has_role_attribute(pg_authid.oid, 'REPLICATION') AS rolreplication,
!         has_role_attribute(pg_authid.oid, 'BYPASSRLS') AS rolbypassrls,
          rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
          setconfig as rolconfig,
          pg_authid.oid
      FROM pg_authid LEFT JOIN pg_db_role_setting s
*************** CREATE VIEW pg_shadow AS
*** 29,44 ****
      SELECT
          rolname AS usename,
          pg_authid.oid AS usesysid,
!         rolcreatedb AS usecreatedb,
!         rolsuper AS usesuper,
!         rolcatupdate AS usecatupd,
!         rolreplication AS userepl,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          setconfig AS useconfig
      FROM pg_authid LEFT JOIN pg_db_role_setting s
      ON (pg_authid.oid = setrole AND setdatabase = 0)
!     WHERE rolcanlogin;
  
  REVOKE ALL on pg_shadow FROM public;
  
--- 29,44 ----
      SELECT
          rolname AS usename,
          pg_authid.oid AS usesysid,
!         has_role_attribute(pg_authid.oid, 'CREATEDB') AS usecreatedb,
!         has_role_attribute(pg_authid.oid, 'SUPERUSER') AS usesuper,
!         has_role_attribute(pg_authid.oid, 'CATUPDATE') AS usecatupd,
!         has_role_attribute(pg_authid.oid, 'REPLICATION') AS userepl,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          setconfig AS useconfig
      FROM pg_authid LEFT JOIN pg_db_role_setting s
      ON (pg_authid.oid = setrole AND setdatabase = 0)
!     WHERE has_role_attribute(pg_authid.oid, 'CANLOGIN');
  
  REVOKE ALL on pg_shadow FROM public;
  
*************** CREATE VIEW pg_group AS
*** 48,54 ****
          oid AS grosysid,
          ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
      FROM pg_authid
!     WHERE NOT rolcanlogin;
  
  CREATE VIEW pg_user AS
      SELECT
--- 48,54 ----
          oid AS grosysid,
          ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
      FROM pg_authid
!     WHERE NOT has_role_attribute(pg_authid.oid, 'CANLOGIN');
  
  CREATE VIEW pg_user AS
      SELECT
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
new file mode 100644
index 94c82d3..78dae2d
*** a/src/backend/commands/dbcommands.c
--- b/src/backend/commands/dbcommands.c
*************** get_db_info(const char *name, LOCKMODE l
*** 1812,1831 ****
  static bool
  have_createdb_privilege(void)
  {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
  	/* Superusers can always do everything */
  	if (superuser())
  		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId()));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreatedb;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  /*
--- 1812,1822 ----
  static bool
  have_createdb_privilege(void)
  {
  	/* Superusers can always do everything */
  	if (superuser())
  		return true;
  
! 	return role_has_attribute(GetUserId(), ROLE_ATTR_CREATEDB);
  }
  
  /*
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
new file mode 100644
index 1a73fd8..72c5dcc
*** a/src/backend/commands/user.c
--- b/src/backend/commands/user.c
*************** have_createrole_privilege(void)
*** 63,68 ****
--- 63,73 ----
  	return has_createrole_privilege(GetUserId());
  }
  
+ static RoleAttr
+ set_role_attribute(RoleAttr attributes, RoleAttr attribute)
+ {
+ 	return ((attributes & ~(0xFFFFFFFFFFFFFFFF)) | attribute);
+ }
  
  /*
   * CREATE ROLE
*************** CreateRole(CreateRoleStmt *stmt)
*** 81,93 ****
  	char	   *password = NULL;	/* user password */
  	bool		encrypt_password = Password_encryption; /* encrypt password? */
  	char		encrypted_password[MD5_PASSWD_LEN + 1];
! 	bool		issuper = false;	/* Make the user a superuser? */
! 	bool		inherit = true; /* Auto inherit privileges? */
  	bool		createrole = false;		/* Can this user create roles? */
  	bool		createdb = false;		/* Can the user create databases? */
  	bool		canlogin = false;		/* Can this user login? */
  	bool		isreplication = false;	/* Is this a replication role? */
  	bool		bypassrls = false;		/* Is this a row security enabled role? */
  	int			connlimit = -1; /* maximum connections allowed */
  	List	   *addroleto = NIL;	/* roles to make this a member of */
  	List	   *rolemembers = NIL;		/* roles to be members of this role */
--- 86,99 ----
  	char	   *password = NULL;	/* user password */
  	bool		encrypt_password = Password_encryption; /* encrypt password? */
  	char		encrypted_password[MD5_PASSWD_LEN + 1];
! 	bool		issuper = false;		/* Make the user a superuser? */
! 	bool		inherit = true;			/* Auto inherit privileges? */
  	bool		createrole = false;		/* Can this user create roles? */
  	bool		createdb = false;		/* Can the user create databases? */
  	bool		canlogin = false;		/* Can this user login? */
  	bool		isreplication = false;	/* Is this a replication role? */
  	bool		bypassrls = false;		/* Is this a row security enabled role? */
+ 	RoleAttr	attributes = ROLE_ATTR_NONE;	/* role attributes, initialized to none. */
  	int			connlimit = -1; /* maximum connections allowed */
  	List	   *addroleto = NIL;	/* roles to make this a member of */
  	List	   *rolemembers = NIL;		/* roles to be members of this role */
*************** CreateRole(CreateRoleStmt *stmt)
*** 249,254 ****
--- 255,262 ----
  
  	if (dpassword && dpassword->arg)
  		password = strVal(dpassword->arg);
+ 
+ 	/* Role Attributes */
  	if (dissuper)
  		issuper = intVal(dissuper->arg) != 0;
  	if (dinherit)
*************** CreateRole(CreateRoleStmt *stmt)
*** 261,266 ****
--- 269,277 ----
  		canlogin = intVal(dcanlogin->arg) != 0;
  	if (disreplication)
  		isreplication = intVal(disreplication->arg) != 0;
+ 	if (dbypassRLS)
+ 		bypassrls = intVal(dbypassRLS->arg) != 0;
+ 
  	if (dconnlimit)
  	{
  		connlimit = intVal(dconnlimit->arg);
*************** CreateRole(CreateRoleStmt *stmt)
*** 277,284 ****
  		adminmembers = (List *) dadminmembers->arg;
  	if (dvalidUntil)
  		validUntil = strVal(dvalidUntil->arg);
- 	if (dbypassRLS)
- 		bypassrls = intVal(dbypassRLS->arg) != 0;
  
  	/* Check some permissions first */
  	if (issuper)
--- 288,293 ----
*************** CreateRole(CreateRoleStmt *stmt)
*** 355,360 ****
--- 364,385 ----
  								validUntil_datum,
  								validUntil_null);
  
+ 	/* Set all role attributes */
+ 	if (issuper)
+ 		attributes |= ROLE_ATTR_SUPERUSER;
+ 	if (inherit)
+ 		attributes |= ROLE_ATTR_INHERIT;
+ 	if (createrole)
+ 		attributes |= ROLE_ATTR_CREATEROLE;
+ 	if (createdb)
+ 		attributes |= ROLE_ATTR_CREATEDB;
+ 	if (canlogin)
+ 		attributes |= ROLE_ATTR_CANLOGIN;
+ 	if (isreplication)
+ 		attributes |= ROLE_ATTR_REPLICATION;
+ 	if (bypassrls)
+ 		attributes |= ROLE_ATTR_BYPASSRLS;
+ 
  	/*
  	 * Build a tuple to insert
  	 */
*************** CreateRole(CreateRoleStmt *stmt)
*** 364,377 ****
  	new_record[Anum_pg_authid_rolname - 1] =
  		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
  
! 	new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
! 	new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
! 	new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
! 	new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
! 	/* superuser gets catupdate right by default */
! 	new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper);
! 	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
! 	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
  	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
  
  	if (password)
--- 389,396 ----
  	new_record[Anum_pg_authid_rolname - 1] =
  		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
  
! 	new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
! 
  	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
  
  	if (password)
*************** CreateRole(CreateRoleStmt *stmt)
*** 394,401 ****
  	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
  	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
  
- 	new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls);
- 
  	tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
  
  	/*
--- 413,418 ----
*************** AlterRole(AlterRoleStmt *stmt)
*** 508,513 ****
--- 525,531 ----
  	DefElem    *dvalidUntil = NULL;
  	DefElem    *dbypassRLS = NULL;
  	Oid			roleid;
+ 	RoleAttr attributes;
  
  	/* Extract options from the statement node tree */
  	foreach(option, stmt->options)
*************** AlterRole(AlterRoleStmt *stmt)
*** 661,681 ****
  	 * To mess with a superuser you gotta be superuser; else you need
  	 * createrole, or just want to change your own password
  	 */
! 	if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper || issuper >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter superusers")));
  	}
! 	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolreplication || isreplication >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter replication users")));
  	}
! 	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolbypassrls || bypassrls >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
--- 679,702 ----
  	 * To mess with a superuser you gotta be superuser; else you need
  	 * createrole, or just want to change your own password
  	 */
! 
! 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 
! 	if (((attributes & ROLE_ATTR_SUPERUSER) > 0) || issuper >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter superusers")));
  	}
! 	else if (((attributes & ROLE_ATTR_REPLICATION) > 0) || isreplication >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter replication users")));
  	}
! 	else if (((attributes & ROLE_ATTR_BYPASSRLS) > 0) || bypassrls >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
*************** AlterRole(AlterRoleStmt *stmt)
*** 743,785 ****
  	 */
  	if (issuper >= 0)
  	{
! 		new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);
! 		new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
! 
! 		new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);
! 		new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true;
  	}
  
  	if (inherit >= 0)
  	{
! 		new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
! 		new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
  	}
  
  	if (createrole >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);
! 		new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
  	}
  
  	if (createdb >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0);
! 		new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true;
  	}
  
  	if (canlogin >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0);
! 		new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true;
  	}
  
  	if (isreplication >= 0)
  	{
! 		new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication > 0);
! 		new_record_repl[Anum_pg_authid_rolreplication - 1] = true;
  	}
  
  	if (dconnlimit)
  	{
  		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
--- 764,821 ----
  	 */
  	if (issuper >= 0)
  	{
! 		attributes = set_role_attribute(attributes,
! 							(issuper > 0) ? (ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE) :
! 							~(ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE));
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (inherit >= 0)
  	{
! 		attributes = set_role_attribute(attributes,
! 							(inherit > 0) ? ROLE_ATTR_INHERIT : ~(ROLE_ATTR_INHERIT));
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (createrole >= 0)
  	{
! 		attributes = set_role_attribute(attributes,
! 							(createrole > 0) ? ROLE_ATTR_CREATEROLE : ~(ROLE_ATTR_CREATEROLE));
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (createdb >= 0)
  	{
! 		attributes = set_role_attribute(attributes,
! 							(createdb > 0) ? ROLE_ATTR_CREATEDB : ~(ROLE_ATTR_CREATEDB));
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (canlogin >= 0)
  	{
! 		attributes = set_role_attribute(attributes,
! 							(canlogin > 0) ? ROLE_ATTR_CANLOGIN : ~(ROLE_ATTR_CANLOGIN));
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (isreplication >= 0)
  	{
! 		attributes = set_role_attribute(attributes,
! 							(isreplication > 0) ? ROLE_ATTR_REPLICATION : ~(ROLE_ATTR_REPLICATION));
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
+ 	if (bypassrls >= 0)
+ 	{
+ 		attributes = set_role_attribute(attributes,
+ 							(bypassrls > 0) ? ROLE_ATTR_BYPASSRLS : ~(ROLE_ATTR_BYPASSRLS));
+ 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
+ 	}
+ 
+ 	/* If any role attributes were set, then update. */
+ 	if (new_record_repl[Anum_pg_authid_rolattr - 1])
+ 		new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
+ 
  	if (dconnlimit)
  	{
  		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
*************** AlterRole(AlterRoleStmt *stmt)
*** 815,825 ****
  	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
  	new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
  
- 	if (bypassrls >= 0)
- 	{
- 		new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls > 0);
- 		new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
- 	}
  
  	new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
  								  new_record_nulls, new_record_repl);
--- 851,856 ----
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 867,872 ****
--- 898,904 ----
  	HeapTuple	roletuple;
  	Oid			databaseid = InvalidOid;
  	Oid			roleid = InvalidOid;
+ 	RoleAttr	attributes;
  
  	if (stmt->role)
  	{
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 889,895 ****
  		 * To mess with a superuser you gotta be superuser; else you need
  		 * createrole, or just want to change your own settings
  		 */
! 		if (((Form_pg_authid) GETSTRUCT(roletuple))->rolsuper)
  		{
  			if (!superuser())
  				ereport(ERROR,
--- 921,928 ----
  		 * To mess with a superuser you gotta be superuser; else you need
  		 * createrole, or just want to change your own settings
  		 */
! 		attributes = ((Form_pg_authid) GETSTRUCT(roletuple))->rolattr;
! 		if ((attributes & ROLE_ATTR_SUPERUSER) > 0)
  		{
  			if (!superuser())
  				ereport(ERROR,
*************** DropRole(DropRoleStmt *stmt)
*** 973,978 ****
--- 1006,1012 ----
  		char	   *detail_log;
  		SysScanDesc sscan;
  		Oid			roleid;
+ 		RoleAttr	attributes;
  
  		tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
  		if (!HeapTupleIsValid(tuple))
*************** DropRole(DropRoleStmt *stmt)
*** 1013,1020 ****
  		 * roles but not superuser roles.  This is mainly to avoid the
  		 * scenario where you accidentally drop the last superuser.
  		 */
! 		if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper &&
! 			!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to drop superusers")));
--- 1047,1054 ----
  		 * roles but not superuser roles.  This is mainly to avoid the
  		 * scenario where you accidentally drop the last superuser.
  		 */
! 		attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 		if (((attributes & ROLE_ATTR_SUPERUSER) > 0) && !superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to drop superusers")));
*************** RenameRole(const char *oldname, const ch
*** 1128,1133 ****
--- 1162,1168 ----
  	bool		repl_repl[Natts_pg_authid];
  	int			i;
  	Oid			roleid;
+ 	RoleAttr	attributes;
  
  	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
  	dsc = RelationGetDescr(rel);
*************** RenameRole(const char *oldname, const ch
*** 1173,1179 ****
  	/*
  	 * createrole is enough privilege unless you want to mess with a superuser
  	 */
! 	if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
  	{
  		if (!superuser())
  			ereport(ERROR,
--- 1208,1215 ----
  	/*
  	 * createrole is enough privilege unless you want to mess with a superuser
  	 */
! 	attributes = ((Form_pg_authid) GETSTRUCT(oldtuple))->rolattr;
! 	if ((attributes & ROLE_ATTR_SUPERUSER) > 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
new file mode 100644
index 6ce8dae..a8a9f2e
*** a/src/backend/commands/variable.c
--- b/src/backend/commands/variable.c
*************** check_session_authorization(char **newva
*** 776,781 ****
--- 776,782 ----
  	Oid			roleid;
  	bool		is_superuser;
  	role_auth_extra *myextra;
+ 	RoleAttr	attributes;
  
  	/* Do nothing for the boot_val default of NULL */
  	if (*newval == NULL)
*************** check_session_authorization(char **newva
*** 800,806 ****
  	}
  
  	roleid = HeapTupleGetOid(roleTup);
! 	is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
  
  	ReleaseSysCache(roleTup);
  
--- 801,808 ----
  	}
  
  	roleid = HeapTupleGetOid(roleTup);
! 	attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
! 	is_superuser = ((attributes & ROLE_ATTR_SUPERUSER) > 0);
  
  	ReleaseSysCache(roleTup);
  
*************** check_role(char **newval, void **extra,
*** 844,849 ****
--- 846,852 ----
  	Oid			roleid;
  	bool		is_superuser;
  	role_auth_extra *myextra;
+ 	RoleAttr	attributes;
  
  	if (strcmp(*newval, "none") == 0)
  	{
*************** check_role(char **newval, void **extra,
*** 872,878 ****
  		}
  
  		roleid = HeapTupleGetOid(roleTup);
! 		is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
  
  		ReleaseSysCache(roleTup);
  
--- 875,882 ----
  		}
  
  		roleid = HeapTupleGetOid(roleTup);
! 		attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
! 		is_superuser = ((attributes & ROLE_ATTR_SUPERUSER) > 0);
  
  		ReleaseSysCache(roleTup);
  
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
new file mode 100644
index dc6eb2c..f3108a5
*** a/src/backend/utils/adt/acl.c
--- b/src/backend/utils/adt/acl.c
*************** static Oid	convert_type_name(text *typen
*** 115,120 ****
--- 115,121 ----
  static AclMode convert_type_priv_string(text *priv_type_text);
  static AclMode convert_role_priv_string(text *priv_type_text);
  static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
+ static RoleAttr convert_role_attr_string(text *attr_type_text);
  
  static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue);
  
*************** aclitemin(PG_FUNCTION_ARGS)
*** 577,582 ****
--- 578,584 ----
  	PG_RETURN_ACLITEM_P(aip);
  }
  
+ 
  /*
   * aclitemout
   *		Allocates storage for, and fills in, a new null-delimited string
*************** pg_role_aclcheck(Oid role_oid, Oid rolei
*** 4602,4607 ****
--- 4604,4655 ----
  	return ACLCHECK_NO_PRIV;
  }
  
+ /*
+  * has_role_attribute_id
+  *		Check the named role attribute on a role by given role oid.
+  */
+ Datum
+ has_role_attribute_id(PG_FUNCTION_ARGS)
+ {
+ 	Oid			roleoid = PG_GETARG_OID(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(role_has_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * convert_role_attr_string
+  *		Convert text string to RoleAttr value.
+  */
+ static RoleAttr
+ convert_role_attr_string(text *attr_type_text)
+ {
+ 	char	   *attr_type = text_to_cstring(attr_type_text);
+ 
+ 	if (pg_strcasecmp(attr_type, "SUPERUSER") == 0)
+ 		return ROLE_ATTR_SUPERUSER;
+ 	else if (pg_strcasecmp(attr_type, "INHERIT") == 0)
+ 		return ROLE_ATTR_INHERIT;
+ 	else if (pg_strcasecmp(attr_type, "CREATEROLE") == 0)
+ 		return ROLE_ATTR_CREATEROLE;
+ 	else if (pg_strcasecmp(attr_type, "CREATEDB") == 0)
+ 		return ROLE_ATTR_CREATEDB;
+ 	else if (pg_strcasecmp(attr_type, "CATUPDATE") == 0)
+ 		return ROLE_ATTR_CATUPDATE;
+ 	else if (pg_strcasecmp(attr_type, "CANLOGIN") == 0)
+ 		return ROLE_ATTR_CANLOGIN;
+ 	else if (pg_strcasecmp(attr_type, "REPLICATION") == 0)
+ 		return ROLE_ATTR_REPLICATION;
+ 	else if (pg_strcasecmp(attr_type, "BYPASSRLS") == 0)
+ 		return ROLE_ATTR_BYPASSRLS;
+ 	else
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("unrecognized role attribute: \"%s\"", attr_type)));
+ }
  
  /*
   * initialization function (called by InitPostgres)
*************** RoleMembershipCacheCallback(Datum arg, i
*** 4638,4653 ****
  static bool
  has_rolinherit(Oid roleid)
  {
! 	bool		result = false;
  	HeapTuple	utup;
  
  	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(utup))
  	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
  		ReleaseSysCache(utup);
  	}
! 	return result;
  }
  
  
--- 4686,4701 ----
  static bool
  has_rolinherit(Oid roleid)
  {
! 	RoleAttr	attributes;
  	HeapTuple	utup;
  
  	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(utup))
  	{
! 		attributes = ((Form_pg_authid) GETSTRUCT(utup))->rolattr;
  		ReleaseSysCache(utup);
  	}
! 	return ((attributes & ROLE_ATTR_INHERIT) > 0);
  }
  
  
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
new file mode 100644
index 8fccb4c..51bf035
*** a/src/backend/utils/init/miscinit.c
--- b/src/backend/utils/init/miscinit.c
*************** SetUserIdAndContext(Oid userid, bool sec
*** 334,349 ****
  bool
  has_rolreplication(Oid roleid)
  {
! 	bool		result = false;
  	HeapTuple	utup;
  
  	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(utup))
  	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication;
  		ReleaseSysCache(utup);
  	}
! 	return result;
  }
  
  /*
--- 334,349 ----
  bool
  has_rolreplication(Oid roleid)
  {
! 	RoleAttr	attributes;
  	HeapTuple	utup;
  
  	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(utup))
  	{
! 		attributes = ((Form_pg_authid) GETSTRUCT(utup))->rolattr;
  		ReleaseSysCache(utup);
  	}
! 	return ((attributes & ROLE_ATTR_REPLICATION) > 0);
  }
  
  /*
*************** InitializeSessionUserId(const char *role
*** 375,381 ****
  	roleid = HeapTupleGetOid(roleTup);
  
  	AuthenticatedUserId = roleid;
! 	AuthenticatedUserIsSuperuser = rform->rolsuper;
  
  	/* This sets OuterUserId/CurrentUserId too */
  	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
--- 375,381 ----
  	roleid = HeapTupleGetOid(roleTup);
  
  	AuthenticatedUserId = roleid;
! 	AuthenticatedUserIsSuperuser = ((rform->rolattr & ROLE_ATTR_SUPERUSER) > 0);
  
  	/* This sets OuterUserId/CurrentUserId too */
  	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
*************** InitializeSessionUserId(const char *role
*** 394,400 ****
  		/*
  		 * Is role allowed to login at all?
  		 */
! 		if (!rform->rolcanlogin)
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  					 errmsg("role \"%s\" is not permitted to log in",
--- 394,400 ----
  		/*
  		 * Is role allowed to login at all?
  		 */
! 		if (!((rform->rolattr & ROLE_ATTR_CANLOGIN) > 0))
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  					 errmsg("role \"%s\" is not permitted to log in",
diff --git a/src/backend/utils/misc/superuser.c b/src/backend/utils/misc/superuser.c
new file mode 100644
index ff0f947..c1ea1c4
*** a/src/backend/utils/misc/superuser.c
--- b/src/backend/utils/misc/superuser.c
*************** superuser_arg(Oid roleid)
*** 58,63 ****
--- 58,64 ----
  {
  	bool		result;
  	HeapTuple	rtup;
+ 	RoleAttr	attributes;
  
  	/* Quick out for cache hit */
  	if (OidIsValid(last_roleid) && last_roleid == roleid)
*************** superuser_arg(Oid roleid)
*** 71,77 ****
  	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(rtup))
  	{
! 		result = ((Form_pg_authid) GETSTRUCT(rtup))->rolsuper;
  		ReleaseSysCache(rtup);
  	}
  	else
--- 72,79 ----
  	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(rtup))
  	{
! 		attributes = ((Form_pg_authid) GETSTRUCT(rtup))->rolattr;
! 		result = ((attributes & ROLE_ATTR_SUPERUSER) > 0);
  		ReleaseSysCache(rtup);
  	}
  	else
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
new file mode 100644
index 3b63d2b..54c3098
*** a/src/include/catalog/pg_authid.h
--- b/src/include/catalog/pg_authid.h
***************
*** 22,27 ****
--- 22,28 ----
  #define PG_AUTHID_H
  
  #include "catalog/genbki.h"
+ #include "nodes/parsenodes.h"
  
  /*
   * The CATALOG definition has to refer to the type of rolvaliduntil as
***************
*** 45,60 ****
  CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
  {
  	NameData	rolname;		/* name of role */
! 	bool		rolsuper;		/* read this field via superuser() only! */
! 	bool		rolinherit;		/* inherit privileges from other roles? */
! 	bool		rolcreaterole;	/* allowed to create more roles? */
! 	bool		rolcreatedb;	/* allowed to create databases? */
! 	bool		rolcatupdate;	/* allowed to alter catalogs manually? */
! 	bool		rolcanlogin;	/* allowed to log in as session user? */
! 	bool		rolreplication; /* role used for streaming replication */
! 	bool		rolbypassrls;	/* allowed to bypass row level security? */
  	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
- 
  	/* remaining fields may be null; use heap_getattr to read them! */
  	text		rolpassword;	/* password, if any */
  	timestamptz rolvaliduntil;	/* password expiration time, if any */
--- 46,53 ----
  CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
  {
  	NameData	rolname;		/* name of role */
! 	int64		rolattr;		/* role attribute bitmask */
  	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
  	/* remaining fields may be null; use heap_getattr to read them! */
  	text		rolpassword;	/* password, if any */
  	timestamptz rolvaliduntil;	/* password expiration time, if any */
*************** typedef FormData_pg_authid *Form_pg_auth
*** 74,92 ****
   *		compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid					12
  #define Anum_pg_authid_rolname			1
! #define Anum_pg_authid_rolsuper			2
! #define Anum_pg_authid_rolinherit		3
! #define Anum_pg_authid_rolcreaterole	4
! #define Anum_pg_authid_rolcreatedb		5
! #define Anum_pg_authid_rolcatupdate		6
! #define Anum_pg_authid_rolcanlogin		7
! #define Anum_pg_authid_rolreplication	8
! #define Anum_pg_authid_rolbypassrls		9
! #define Anum_pg_authid_rolconnlimit		10
! #define Anum_pg_authid_rolpassword		11
! #define Anum_pg_authid_rolvaliduntil	12
  
  /* ----------------
   *		initial contents of pg_authid
--- 67,78 ----
   *		compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid					5
  #define Anum_pg_authid_rolname			1
! #define Anum_pg_authid_rolattr			2
! #define Anum_pg_authid_rolconnlimit		3
! #define Anum_pg_authid_rolpassword		4
! #define Anum_pg_authid_rolvaliduntil	5
  
  /* ----------------
   *		initial contents of pg_authid
*************** typedef FormData_pg_authid *Form_pg_auth
*** 95,101 ****
   * user choices.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t -1 _null_ _null_));
  
  #define BOOTSTRAP_SUPERUSERID 10
  
--- 81,87 ----
   * user choices.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" 255 -1 _null_ _null_));
  
  #define BOOTSTRAP_SUPERUSERID 10
  
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index 5d4e889..c818e51
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("current user privilege on any col
*** 2676,2681 ****
--- 2676,2684 ----
  DATA(insert OID = 3029 (  has_any_column_privilege	   PGNSP PGUID 12 10 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ has_any_column_privilege_id _null_ _null_ _null_ ));
  DESCR("current user privilege on any column by rel oid");
  
+ DATA(insert OID = 6000 (  has_role_attribute		   PGNSP PGUID 12 10 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ has_role_attribute_id _null_ _null_ _null_));
+ DESCR("user role attribute by user oid");
+ 
  DATA(insert OID = 1928 (  pg_stat_get_numscans			PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 20 "26" _null_ _null_ _null_ _null_ pg_stat_get_numscans _null_ _null_ _null_ ));
  DESCR("statistics: number of scans done for table/index");
  DATA(insert OID = 1929 (  pg_stat_get_tuples_returned	PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 20 "26" _null_ _null_ _null_ _null_ pg_stat_get_tuples_returned _null_ _null_ _null_ ));
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 3e4f815..d560d69
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef uint32 AclMode;			/* a bitmask o
*** 78,83 ****
--- 78,101 ----
  /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
  #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
  
+ /*
+  * Role attributes are encoded so that we can OR them together in a bitmask.
+  * The present representation of RoleAttr limits us to 64 distinct rights.
+  *
+  * Caution: changing these codes breaks stored RoleAttrs, hence forces initdb.
+  */
+ typedef uint64 RoleAttr;		/* a bitmask for role attribute bits */
+ 
+ #define ROLE_ATTR_SUPERUSER		(1<<0)
+ #define ROLE_ATTR_INHERIT		(1<<1)
+ #define ROLE_ATTR_CREATEROLE	(1<<2)
+ #define ROLE_ATTR_CREATEDB		(1<<3)
+ #define ROLE_ATTR_CATUPDATE		(1<<4)
+ #define ROLE_ATTR_CANLOGIN		(1<<5)
+ #define ROLE_ATTR_REPLICATION	(1<<6)
+ #define ROLE_ATTR_BYPASSRLS		(1<<7)
+ #define N_ROLE_ATTRIBUTES		8
+ #define ROLE_ATTR_NONE			0
  
  /*****************************************************************************
   *	Query Tree
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
new file mode 100644
index a8e3164..aecad4f
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
*************** extern bool pg_event_trigger_ownercheck(
*** 328,332 ****
--- 328,333 ----
  extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
  extern bool has_createrole_privilege(Oid roleid);
  extern bool has_bypassrls_privilege(Oid roleid);
+ extern bool role_has_attribute(Oid roleid, RoleAttr capability);
  
  #endif   /* ACL_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index 417fd17..31d7559
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum pg_has_role_id_name(PG_FUNC
*** 106,111 ****
--- 106,112 ----
  extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS);
  extern Datum pg_has_role_name(PG_FUNCTION_ARGS);
  extern Datum pg_has_role_id(PG_FUNCTION_ARGS);
+ extern Datum has_role_attribute_id(PG_FUNCTION_ARGS);
  
  /* bool.c */
  extern Datum boolin(PG_FUNCTION_ARGS);
#2Andres Freund
andres@anarazel.de
In reply to: Adam Brightwell (#1)
Re: Role Attribute Bitmask Catalog Representation

On 2014-11-24 15:39:22 -0500, Adam Brightwell wrote:

* int64 (C) to int8 (SQL) mapping for genbki.

That definitely should be a separate patch. Which can be committed much
earlier than the rest - even if we don't actually end up needing it for
this feature, it's still good to have it.

* replace all role attributes columns in pg_authid with single int64 column
named rolattr.
* update CreateRole and AlterRole to use rolattr.
* update all has_*_privilege functions to check rolattr.
* builtin SQL function 'has_role_attribute' that takes a role oid and text
name of the attribute as input and returns a boolean.

I think if we're going to do this - and I'm not yet convinced that
that's the best route, we should add returns all permissions a user
has. Right now that's quite easily queryable, but it won't be after
moving everything into one column. You'd need to manually use all has_*_
functions... Yes, you've added them already to pg_roles, but there's
sometimes good reasons to go to pg_authid instead.

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3David G Johnston
david.g.johnston@gmail.com
In reply to: Adam Brightwell (#1)
Re: Role Attribute Bitmask Catalog Representation

Adam Brightwell wrote

A few related threads/discussions/posts:

* http://www.postgresql.org/message-id/

20141016115914.GQ28859@.snowman

*
/messages/by-id/CA+TgmobkYXNOWKEKzX2qGPSr_nvacFGueV=

orxND-xmZvOVYvg@.gmail

* http://www.postgresql.org/message-id/

20141016115914.GQ28859@.snowman

FYI: the first and third links are the same...was there another one you
meant to provide instead?

David J.

--
View this message in context: http://postgresql.nabble.com/Role-Attribute-Bitmask-Catalog-Representation-tp5828078p5828106.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Andres Freund (#2)
Re: Role Attribute Bitmask Catalog Representation

Andres,

Thanks for the feedback.

* int64 (C) to int8 (SQL) mapping for genbki.

That definitely should be a separate patch. Which can be committed much
earlier than the rest - even if we don't actually end up needing it for
this feature, it's still good to have it.

Agreed. I had previously submitted this as a separate patch, but I think
it got lost in the weeds. At any rate, here is the relevant post:

/messages/by-id/CAKRt6CTgJdeGFqXevrp-DizaeHmg8gNVqu8n5T=ix3JAvpwwDQ@mail.gmail.com

* replace all role attributes columns in pg_authid with single int64

column

named rolattr.
* update CreateRole and AlterRole to use rolattr.
* update all has_*_privilege functions to check rolattr.
* builtin SQL function 'has_role_attribute' that takes a role oid and

text

name of the attribute as input and returns a boolean.

I think if we're going to do this - and I'm not yet convinced that
that's the best route, we should add returns all permissions a user
has. Right now that's quite easily queryable, but it won't be after
moving everything into one column. You'd need to manually use all has_*_
functions... Yes, you've added them already to pg_roles, but there's
sometimes good reasons to go to pg_authid instead.

This is a good point. I'll start looking at this and see what I can come
up with.

An array representation was also suggested by Simon (
/messages/by-id/CA+U5nMJGVdz6jX_YBJk99Nj7mWfGfVEmxtdc44LVHq64gkN8qg@mail.gmail.com).
Obviously there are pro's and con's to either approach. I'm not married to
it, but felt that a bitmask was certainly more efficient. However, I know
that an array would be more extensible given that we could envision more
than 64 role attributes. I'm uncertain if that is a potential reality or
not, but I believe it is certainly worth considering.

-Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

#5Stephen Frost
sfrost@snowman.net
In reply to: Adam Brightwell (#4)
Re: Role Attribute Bitmask Catalog Representation

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

* int64 (C) to int8 (SQL) mapping for genbki.

That definitely should be a separate patch. Which can be committed much
earlier than the rest - even if we don't actually end up needing it for
this feature, it's still good to have it.

Agreed. I had previously submitted this as a separate patch, but I think
it got lost in the weeds. At any rate, here is the relevant post:

/messages/by-id/CAKRt6CTgJdeGFqXevrp-DizaeHmg8gNVqu8n5T=ix3JAvpwwDQ@mail.gmail.com

Yeah, done now.

Thanks,

Stephen

#6Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: David G Johnston (#3)
Re: Role Attribute Bitmask Catalog Representation

David,

A few related threads/discussions/posts:

* http://www.postgresql.org/message-id/

20141016115914.GQ28859@.snowman

*

/messages/by-id/CA+TgmobkYXNOWKEKzX2qGPSr_nvacFGueV=

orxND-xmZvOVYvg@.gmail

* http://www.postgresql.org/message-id/

20141016115914.GQ28859@.snowman

FYI: the first and third links are the same...was there another one you
meant to provide instead?

Whoops. Yes there was, but if I remember correctly it was part of that
same overarching thread. The two provided, I believe are sufficient to
lead to any prior relevant discussions that influenced/motivated this patch.

-Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

#7Stephen Frost
sfrost@snowman.net
In reply to: Adam Brightwell (#4)
Re: Role Attribute Bitmask Catalog Representation

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

An array representation was also suggested by Simon (
/messages/by-id/CA+U5nMJGVdz6jX_YBJk99Nj7mWfGfVEmxtdc44LVHq64gkN8qg@mail.gmail.com).
Obviously there are pro's and con's to either approach. I'm not married to
it, but felt that a bitmask was certainly more efficient. However, I know
that an array would be more extensible given that we could envision more
than 64 role attributes. I'm uncertain if that is a potential reality or
not, but I believe it is certainly worth considering.

I'd be pretty surprised if we actually got up to 64, and if we did we
could change it to a bytea. It wouldn't be the cleanest thing, but
using an array would change pg_authid from "same size as today" to
"quite a bit larger" and I don't really see the advantage. We use a bit
field for the GRANT-based permissions and people have to use functions
to decode those too and while it's not ideal, I don't feel like we hear
people complaining about it.

Thanks,

Stephen

#8Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Adam Brightwell (#4)
Re: Role Attribute Bitmask Catalog Representation

All,

I think if we're going to do this - and I'm not yet convinced that

that's the best route, we should add returns all permissions a user
has. Right now that's quite easily queryable, but it won't be after
moving everything into one column. You'd need to manually use all has_*_
functions... Yes, you've added them already to pg_roles, but there's
sometimes good reasons to go to pg_authid instead.

This is a good point. I'll start looking at this and see what I can come
up with.

Giving this some thought, I'm curious what would be acceptable as an end
result, specifically related to how a query on pg_authid might look/work.
I was able to preserve the structure of results from pg_roles, however,
that same approach is obviously not possible with pg_authid. So, I'm
curious what the thoughts might be on how to best solve this while
minimizing impact (perhaps not possible) on users. Currently, my thought
is to have a builtin function called 'get_all_role_attributes' or similar,
that returns an array of each attribute in string form. My thoughts are
that it might look something like the following:

SELECT rolname, get_all_role_attributes(rolattr) AS attributes FROM
pg_authid;

| rolname | attributes |
+---------+-------------------------------------+
| user1 | {Superuser, Create Role, Create DB} |

Another approach might be that the above function return a string of comma
separated attributes, similar to what \du in psql does. IMO, I think the
array approach would be more appropriate than a string but I'm willing to
accept that neither is acceptable and would certainly be interested in
opinions.

Thanks,
Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

#9Stephen Frost
sfrost@snowman.net
In reply to: Adam Brightwell (#8)
Re: Role Attribute Bitmask Catalog Representation

Adam,

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

Giving this some thought, I'm curious what would be acceptable as an end
result, specifically related to how a query on pg_authid might look/work.
I was able to preserve the structure of results from pg_roles, however,
that same approach is obviously not possible with pg_authid. So, I'm
curious what the thoughts might be on how to best solve this while
minimizing impact (perhaps not possible) on users. Currently, my thought
is to have a builtin function called 'get_all_role_attributes' or similar,
that returns an array of each attribute in string form. My thoughts are
that it might look something like the following:

Having an array sounds pretty reasonable to me.

Another approach might be that the above function return a string of comma
separated attributes, similar to what \du in psql does. IMO, I think the
array approach would be more appropriate than a string but I'm willing to
accept that neither is acceptable and would certainly be interested in
opinions.

Users interested in having a string instead could use array_to_string().
Having to go the other way isn't as nice, imo.

Thanks!

Stephen

#10Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Stephen Frost (#9)
Re: Role Attribute Bitmask Catalog Representation

Stephen,

Having an array sounds pretty reasonable to me.

Ok, sounds good, I think so too.

Users interested in having a string instead could use array_to_string().

Having to go the other way isn't as nice, imo.

My thoughts exactly, but wanted to at least put it out there.

Thanks,
Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

#11Stephen Frost
sfrost@snowman.net
In reply to: Adam Brightwell (#1)
Re: Role Attribute Bitmask Catalog Representation

Adam,

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

I am simply breaking this out into its own thread from the discussion on
additional role attributes (
/messages/by-id/20141015052259.GG28859@tamriel.snowman.net
).

Makes sense to me, thanks.

Based on these above I have attached an initial WIP patch for review and
discussion that takes a swing at changing the catalog representation.

Just a quick initial look, but I don't think we want to #include
parsenodes.h into pg_authid.h. Why not put the #define ROLE_ATTR_* into
pg_authid.h instead? We have similar #define's in other catalog .h's
(PROARGMODE_*, RELKIND_*, etc).

I'm also not a huge fan of the hard-coded 255 for the default superuser.
That goes back to the other question of if we should bother having those
explicitly listed at all, but I'd suggest having a #define for 'all'
bits to be true (for currently used bits) and then a comment above the
superuser which references that #define (we can't use the #define
directly or we'd be including pg_authid.h into pg_proc.h, which isn't a
good idea either; if we really want to use the #define, genbki.pl could
be adjusted to find the #define similar to what it does for PGUID and
PGNSP).

Thanks!

Stephen

#12Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Stephen Frost (#11)
1 attachment(s)
Re: Role Attribute Bitmask Catalog Representation

All,

I have attached a patch that addresses the current suggestions and
recommendations:

* Add 'get_all_role_attributes' SQL function - returns a text array
representation of the attributes from a value passed to it.

Example:

postgres=# SELECT rolname, get_all_role_attributes(rolattr) AS rolattr FROM
pg_authid;
rolname | rolattr

----------+-----------------------------------------------------------------------------------------------
postgres | {Superuser,Inherit,"Create Role","Create DB","Catalog
Update",Login,Replication,"Bypass RLS"}
(1 row)

* Refactor #define's from 'parsenodes.h' to 'acl.h'
* Added #define ROLE_ATTR_ALL to represent all currently available
attributes.
* Added genbki.pl substitution for PGROLEATTRALL constant.

Please let me know what you think, all feedback is greatly appreciated.

Thanks,
Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

Attachments:

role-attribute-bitmask-v2.patchtext/x-patch; charset=US-ASCII; name=role-attribute-bitmask-v2.patchDownload
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
new file mode 100644
index d30612c..93eb2e6
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
*************** aclcheck_error_type(AclResult aclerr, Oi
*** 3423,3448 ****
  }
  
  
- /* Check if given user has rolcatupdate privilege according to pg_authid */
- static bool
- has_rolcatupdate(Oid roleid)
- {
- 	bool		rolcatupdate;
- 	HeapTuple	tuple;
- 
- 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (!HeapTupleIsValid(tuple))
- 		ereport(ERROR,
- 				(errcode(ERRCODE_UNDEFINED_OBJECT),
- 				 errmsg("role with OID %u does not exist", roleid)));
- 
- 	rolcatupdate = ((Form_pg_authid) GETSTRUCT(tuple))->rolcatupdate;
- 
- 	ReleaseSysCache(tuple);
- 
- 	return rolcatupdate;
- }
- 
  /*
   * Relay for the various pg_*_mask routines depending on object kind
   */
--- 3423,3428 ----
*************** pg_class_aclmask(Oid table_oid, Oid role
*** 3630,3636 ****
  	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
  		IsSystemClass(table_oid, classForm) &&
  		classForm->relkind != RELKIND_VIEW &&
! 		!has_rolcatupdate(roleid) &&
  		!allowSystemTableMods)
  	{
  #ifdef ACLDEBUG
--- 3610,3616 ----
  	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
  		IsSystemClass(table_oid, classForm) &&
  		classForm->relkind != RELKIND_VIEW &&
! 		!role_has_attribute(roleid, ROLE_ATTR_CATUPDATE) &&
  		!allowSystemTableMods)
  	{
  #ifdef ACLDEBUG
*************** pg_extension_ownercheck(Oid ext_oid, Oid
*** 5051,5056 ****
--- 5031,5058 ----
  }
  
  /*
+  * Check whether the specified role has a specific role attribute.
+  */
+ bool
+ role_has_attribute(Oid roleid, RoleAttr attribute)
+ {
+ 	RoleAttr	attributes;
+ 	HeapTuple	tuple;
+ 
+ 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
+ 
+ 	if (!HeapTupleIsValid(tuple))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("role with OID %u does not exist", roleid)));
+ 
+ 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
+ 	ReleaseSysCache(tuple);
+ 
+ 	return ((attributes & attribute) > 0);
+ }
+ 
+ /*
   * Check whether specified role has CREATEROLE privilege (or is a superuser)
   *
   * Note: roles do not have owners per se; instead we use this test in
*************** pg_extension_ownercheck(Oid ext_oid, Oid
*** 5064,5102 ****
  bool
  has_createrole_privilege(Oid roleid)
  {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
  	/* Superusers bypass all permission checking. */
  	if (superuser_arg(roleid))
  		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  bool
  has_bypassrls_privilege(Oid roleid)
  {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
  	/* Superusers bypass all permission checking. */
  	if (superuser_arg(roleid))
  		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolbypassrls;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  /*
--- 5066,5089 ----
  bool
  has_createrole_privilege(Oid roleid)
  {
  	/* Superusers bypass all permission checking. */
  	if (superuser_arg(roleid))
  		return true;
  
! 	return role_has_attribute(roleid, ROLE_ATTR_CREATEROLE);
  }
  
+ /*
+  * Check whether specified role has BYPASSRLS privilege.
+  */
  bool
  has_bypassrls_privilege(Oid roleid)
  {
  	/* Superusers bypass all permission checking. */
  	if (superuser_arg(roleid))
  		return true;
  
! 	return role_has_attribute(roleid, ROLE_ATTR_BYPASSRLS);
  }
  
  /*
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
new file mode 100644
index ca89879..2929b66
*** a/src/backend/catalog/genbki.pl
--- b/src/backend/catalog/genbki.pl
*************** my $BOOTSTRAP_SUPERUSERID =
*** 90,95 ****
--- 90,97 ----
    find_defined_symbol('pg_authid.h', 'BOOTSTRAP_SUPERUSERID');
  my $PG_CATALOG_NAMESPACE =
    find_defined_symbol('pg_namespace.h', 'PG_CATALOG_NAMESPACE');
+ my $ROLE_ATTR_ALL =
+   find_defined_symbol('pg_authid.h', 'ROLE_ATTR_ALL');
  
  # Read all the input header files into internal data structures
  my $catalogs = Catalog::Catalogs(@input_files);
*************** foreach my $catname (@{ $catalogs->{name
*** 144,149 ****
--- 146,152 ----
  			# substitute constant values we acquired above
  			$row->{bki_values} =~ s/\bPGUID\b/$BOOTSTRAP_SUPERUSERID/g;
  			$row->{bki_values} =~ s/\bPGNSP\b/$PG_CATALOG_NAMESPACE/g;
+ 			$row->{bki_values} =~ s/\bPGROLATTRALL/$ROLE_ATTR_ALL/g;
  
  			# Save pg_type info for pg_attribute processing below
  			if ($catname eq 'pg_type')
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
new file mode 100644
index a036c62..6904716
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
*************** CREATE VIEW user_mapping_options AS
*** 2884,2890 ****
             CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
             CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                         OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
!                        OR (SELECT rolsuper FROM pg_authid WHERE rolname = current_user) THEN (pg_options_to_table(um.umoptions)).option_value
                       ELSE NULL END AS character_data) AS option_value
      FROM _pg_user_mappings um;
  
--- 2884,2895 ----
             CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
             CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                         OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
!                        OR (
!                             SELECT has_role_attribute(pg_authid.oid, 'SUPERUSER') AS rolsuper
!                             FROM pg_authid
!                             WHERE rolname = current_user
!                           )
!                        THEN (pg_options_to_table(um.umoptions)).option_value
                       ELSE NULL END AS character_data) AS option_value
      FROM _pg_user_mappings um;
  
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
new file mode 100644
index 22b8cee..1fbcc27
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 9,25 ****
  CREATE VIEW pg_roles AS
      SELECT
          rolname,
!         rolsuper,
!         rolinherit,
!         rolcreaterole,
!         rolcreatedb,
!         rolcatupdate,
!         rolcanlogin,
!         rolreplication,
          rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
-         rolbypassrls,
          setconfig as rolconfig,
          pg_authid.oid
      FROM pg_authid LEFT JOIN pg_db_role_setting s
--- 9,25 ----
  CREATE VIEW pg_roles AS
      SELECT
          rolname,
!         has_role_attribute(pg_authid.oid, 'SUPERUSER') AS rolsuper,
!         has_role_attribute(pg_authid.oid, 'INHERIT') AS rolinherit,
!         has_role_attribute(pg_authid.oid, 'CREATEROLE') AS rolcreaterole,
!         has_role_attribute(pg_authid.oid, 'CREATEDB') AS rolcreatedb,
!         has_role_attribute(pg_authid.oid, 'CATUPDATE') AS rolcatupdate,
!         has_role_attribute(pg_authid.oid, 'CANLOGIN') AS rolcanlogin,
!         has_role_attribute(pg_authid.oid, 'REPLICATION') AS rolreplication,
!         has_role_attribute(pg_authid.oid, 'BYPASSRLS') AS rolbypassrls,
          rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
          setconfig as rolconfig,
          pg_authid.oid
      FROM pg_authid LEFT JOIN pg_db_role_setting s
*************** CREATE VIEW pg_shadow AS
*** 29,44 ****
      SELECT
          rolname AS usename,
          pg_authid.oid AS usesysid,
!         rolcreatedb AS usecreatedb,
!         rolsuper AS usesuper,
!         rolcatupdate AS usecatupd,
!         rolreplication AS userepl,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          setconfig AS useconfig
      FROM pg_authid LEFT JOIN pg_db_role_setting s
      ON (pg_authid.oid = setrole AND setdatabase = 0)
!     WHERE rolcanlogin;
  
  REVOKE ALL on pg_shadow FROM public;
  
--- 29,44 ----
      SELECT
          rolname AS usename,
          pg_authid.oid AS usesysid,
!         has_role_attribute(pg_authid.oid, 'CREATEDB') AS usecreatedb,
!         has_role_attribute(pg_authid.oid, 'SUPERUSER') AS usesuper,
!         has_role_attribute(pg_authid.oid, 'CATUPDATE') AS usecatupd,
!         has_role_attribute(pg_authid.oid, 'REPLICATION') AS userepl,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          setconfig AS useconfig
      FROM pg_authid LEFT JOIN pg_db_role_setting s
      ON (pg_authid.oid = setrole AND setdatabase = 0)
!     WHERE has_role_attribute(pg_authid.oid, 'CANLOGIN');
  
  REVOKE ALL on pg_shadow FROM public;
  
*************** CREATE VIEW pg_group AS
*** 48,54 ****
          oid AS grosysid,
          ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
      FROM pg_authid
!     WHERE NOT rolcanlogin;
  
  CREATE VIEW pg_user AS
      SELECT
--- 48,54 ----
          oid AS grosysid,
          ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
      FROM pg_authid
!     WHERE NOT has_role_attribute(pg_authid.oid, 'CANLOGIN');
  
  CREATE VIEW pg_user AS
      SELECT
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
new file mode 100644
index 1a5244c..f46d69e
*** a/src/backend/commands/dbcommands.c
--- b/src/backend/commands/dbcommands.c
*************** get_db_info(const char *name, LOCKMODE l
*** 1806,1825 ****
  static bool
  have_createdb_privilege(void)
  {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
  	/* Superusers can always do everything */
  	if (superuser())
  		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId()));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreatedb;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  /*
--- 1806,1816 ----
  static bool
  have_createdb_privilege(void)
  {
  	/* Superusers can always do everything */
  	if (superuser())
  		return true;
  
! 	return role_has_attribute(GetUserId(), ROLE_ATTR_CREATEDB);
  }
  
  /*
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
new file mode 100644
index 1a73fd8..72c5dcc
*** a/src/backend/commands/user.c
--- b/src/backend/commands/user.c
*************** have_createrole_privilege(void)
*** 63,68 ****
--- 63,73 ----
  	return has_createrole_privilege(GetUserId());
  }
  
+ static RoleAttr
+ set_role_attribute(RoleAttr attributes, RoleAttr attribute)
+ {
+ 	return ((attributes & ~(0xFFFFFFFFFFFFFFFF)) | attribute);
+ }
  
  /*
   * CREATE ROLE
*************** CreateRole(CreateRoleStmt *stmt)
*** 81,93 ****
  	char	   *password = NULL;	/* user password */
  	bool		encrypt_password = Password_encryption; /* encrypt password? */
  	char		encrypted_password[MD5_PASSWD_LEN + 1];
! 	bool		issuper = false;	/* Make the user a superuser? */
! 	bool		inherit = true; /* Auto inherit privileges? */
  	bool		createrole = false;		/* Can this user create roles? */
  	bool		createdb = false;		/* Can the user create databases? */
  	bool		canlogin = false;		/* Can this user login? */
  	bool		isreplication = false;	/* Is this a replication role? */
  	bool		bypassrls = false;		/* Is this a row security enabled role? */
  	int			connlimit = -1; /* maximum connections allowed */
  	List	   *addroleto = NIL;	/* roles to make this a member of */
  	List	   *rolemembers = NIL;		/* roles to be members of this role */
--- 86,99 ----
  	char	   *password = NULL;	/* user password */
  	bool		encrypt_password = Password_encryption; /* encrypt password? */
  	char		encrypted_password[MD5_PASSWD_LEN + 1];
! 	bool		issuper = false;		/* Make the user a superuser? */
! 	bool		inherit = true;			/* Auto inherit privileges? */
  	bool		createrole = false;		/* Can this user create roles? */
  	bool		createdb = false;		/* Can the user create databases? */
  	bool		canlogin = false;		/* Can this user login? */
  	bool		isreplication = false;	/* Is this a replication role? */
  	bool		bypassrls = false;		/* Is this a row security enabled role? */
+ 	RoleAttr	attributes = ROLE_ATTR_NONE;	/* role attributes, initialized to none. */
  	int			connlimit = -1; /* maximum connections allowed */
  	List	   *addroleto = NIL;	/* roles to make this a member of */
  	List	   *rolemembers = NIL;		/* roles to be members of this role */
*************** CreateRole(CreateRoleStmt *stmt)
*** 249,254 ****
--- 255,262 ----
  
  	if (dpassword && dpassword->arg)
  		password = strVal(dpassword->arg);
+ 
+ 	/* Role Attributes */
  	if (dissuper)
  		issuper = intVal(dissuper->arg) != 0;
  	if (dinherit)
*************** CreateRole(CreateRoleStmt *stmt)
*** 261,266 ****
--- 269,277 ----
  		canlogin = intVal(dcanlogin->arg) != 0;
  	if (disreplication)
  		isreplication = intVal(disreplication->arg) != 0;
+ 	if (dbypassRLS)
+ 		bypassrls = intVal(dbypassRLS->arg) != 0;
+ 
  	if (dconnlimit)
  	{
  		connlimit = intVal(dconnlimit->arg);
*************** CreateRole(CreateRoleStmt *stmt)
*** 277,284 ****
  		adminmembers = (List *) dadminmembers->arg;
  	if (dvalidUntil)
  		validUntil = strVal(dvalidUntil->arg);
- 	if (dbypassRLS)
- 		bypassrls = intVal(dbypassRLS->arg) != 0;
  
  	/* Check some permissions first */
  	if (issuper)
--- 288,293 ----
*************** CreateRole(CreateRoleStmt *stmt)
*** 355,360 ****
--- 364,385 ----
  								validUntil_datum,
  								validUntil_null);
  
+ 	/* Set all role attributes */
+ 	if (issuper)
+ 		attributes |= ROLE_ATTR_SUPERUSER;
+ 	if (inherit)
+ 		attributes |= ROLE_ATTR_INHERIT;
+ 	if (createrole)
+ 		attributes |= ROLE_ATTR_CREATEROLE;
+ 	if (createdb)
+ 		attributes |= ROLE_ATTR_CREATEDB;
+ 	if (canlogin)
+ 		attributes |= ROLE_ATTR_CANLOGIN;
+ 	if (isreplication)
+ 		attributes |= ROLE_ATTR_REPLICATION;
+ 	if (bypassrls)
+ 		attributes |= ROLE_ATTR_BYPASSRLS;
+ 
  	/*
  	 * Build a tuple to insert
  	 */
*************** CreateRole(CreateRoleStmt *stmt)
*** 364,377 ****
  	new_record[Anum_pg_authid_rolname - 1] =
  		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
  
! 	new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
! 	new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
! 	new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
! 	new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
! 	/* superuser gets catupdate right by default */
! 	new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper);
! 	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
! 	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
  	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
  
  	if (password)
--- 389,396 ----
  	new_record[Anum_pg_authid_rolname - 1] =
  		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
  
! 	new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
! 
  	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
  
  	if (password)
*************** CreateRole(CreateRoleStmt *stmt)
*** 394,401 ****
  	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
  	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
  
- 	new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls);
- 
  	tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
  
  	/*
--- 413,418 ----
*************** AlterRole(AlterRoleStmt *stmt)
*** 508,513 ****
--- 525,531 ----
  	DefElem    *dvalidUntil = NULL;
  	DefElem    *dbypassRLS = NULL;
  	Oid			roleid;
+ 	RoleAttr attributes;
  
  	/* Extract options from the statement node tree */
  	foreach(option, stmt->options)
*************** AlterRole(AlterRoleStmt *stmt)
*** 661,681 ****
  	 * To mess with a superuser you gotta be superuser; else you need
  	 * createrole, or just want to change your own password
  	 */
! 	if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper || issuper >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter superusers")));
  	}
! 	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolreplication || isreplication >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter replication users")));
  	}
! 	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolbypassrls || bypassrls >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
--- 679,702 ----
  	 * To mess with a superuser you gotta be superuser; else you need
  	 * createrole, or just want to change your own password
  	 */
! 
! 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 
! 	if (((attributes & ROLE_ATTR_SUPERUSER) > 0) || issuper >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter superusers")));
  	}
! 	else if (((attributes & ROLE_ATTR_REPLICATION) > 0) || isreplication >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter replication users")));
  	}
! 	else if (((attributes & ROLE_ATTR_BYPASSRLS) > 0) || bypassrls >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
*************** AlterRole(AlterRoleStmt *stmt)
*** 743,785 ****
  	 */
  	if (issuper >= 0)
  	{
! 		new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);
! 		new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
! 
! 		new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);
! 		new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true;
  	}
  
  	if (inherit >= 0)
  	{
! 		new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
! 		new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
  	}
  
  	if (createrole >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);
! 		new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
  	}
  
  	if (createdb >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0);
! 		new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true;
  	}
  
  	if (canlogin >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0);
! 		new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true;
  	}
  
  	if (isreplication >= 0)
  	{
! 		new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication > 0);
! 		new_record_repl[Anum_pg_authid_rolreplication - 1] = true;
  	}
  
  	if (dconnlimit)
  	{
  		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
--- 764,821 ----
  	 */
  	if (issuper >= 0)
  	{
! 		attributes = set_role_attribute(attributes,
! 							(issuper > 0) ? (ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE) :
! 							~(ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE));
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (inherit >= 0)
  	{
! 		attributes = set_role_attribute(attributes,
! 							(inherit > 0) ? ROLE_ATTR_INHERIT : ~(ROLE_ATTR_INHERIT));
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (createrole >= 0)
  	{
! 		attributes = set_role_attribute(attributes,
! 							(createrole > 0) ? ROLE_ATTR_CREATEROLE : ~(ROLE_ATTR_CREATEROLE));
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (createdb >= 0)
  	{
! 		attributes = set_role_attribute(attributes,
! 							(createdb > 0) ? ROLE_ATTR_CREATEDB : ~(ROLE_ATTR_CREATEDB));
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (canlogin >= 0)
  	{
! 		attributes = set_role_attribute(attributes,
! 							(canlogin > 0) ? ROLE_ATTR_CANLOGIN : ~(ROLE_ATTR_CANLOGIN));
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (isreplication >= 0)
  	{
! 		attributes = set_role_attribute(attributes,
! 							(isreplication > 0) ? ROLE_ATTR_REPLICATION : ~(ROLE_ATTR_REPLICATION));
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
+ 	if (bypassrls >= 0)
+ 	{
+ 		attributes = set_role_attribute(attributes,
+ 							(bypassrls > 0) ? ROLE_ATTR_BYPASSRLS : ~(ROLE_ATTR_BYPASSRLS));
+ 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
+ 	}
+ 
+ 	/* If any role attributes were set, then update. */
+ 	if (new_record_repl[Anum_pg_authid_rolattr - 1])
+ 		new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
+ 
  	if (dconnlimit)
  	{
  		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
*************** AlterRole(AlterRoleStmt *stmt)
*** 815,825 ****
  	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
  	new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
  
- 	if (bypassrls >= 0)
- 	{
- 		new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls > 0);
- 		new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
- 	}
  
  	new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
  								  new_record_nulls, new_record_repl);
--- 851,856 ----
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 867,872 ****
--- 898,904 ----
  	HeapTuple	roletuple;
  	Oid			databaseid = InvalidOid;
  	Oid			roleid = InvalidOid;
+ 	RoleAttr	attributes;
  
  	if (stmt->role)
  	{
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 889,895 ****
  		 * To mess with a superuser you gotta be superuser; else you need
  		 * createrole, or just want to change your own settings
  		 */
! 		if (((Form_pg_authid) GETSTRUCT(roletuple))->rolsuper)
  		{
  			if (!superuser())
  				ereport(ERROR,
--- 921,928 ----
  		 * To mess with a superuser you gotta be superuser; else you need
  		 * createrole, or just want to change your own settings
  		 */
! 		attributes = ((Form_pg_authid) GETSTRUCT(roletuple))->rolattr;
! 		if ((attributes & ROLE_ATTR_SUPERUSER) > 0)
  		{
  			if (!superuser())
  				ereport(ERROR,
*************** DropRole(DropRoleStmt *stmt)
*** 973,978 ****
--- 1006,1012 ----
  		char	   *detail_log;
  		SysScanDesc sscan;
  		Oid			roleid;
+ 		RoleAttr	attributes;
  
  		tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
  		if (!HeapTupleIsValid(tuple))
*************** DropRole(DropRoleStmt *stmt)
*** 1013,1020 ****
  		 * roles but not superuser roles.  This is mainly to avoid the
  		 * scenario where you accidentally drop the last superuser.
  		 */
! 		if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper &&
! 			!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to drop superusers")));
--- 1047,1054 ----
  		 * roles but not superuser roles.  This is mainly to avoid the
  		 * scenario where you accidentally drop the last superuser.
  		 */
! 		attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 		if (((attributes & ROLE_ATTR_SUPERUSER) > 0) && !superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to drop superusers")));
*************** RenameRole(const char *oldname, const ch
*** 1128,1133 ****
--- 1162,1168 ----
  	bool		repl_repl[Natts_pg_authid];
  	int			i;
  	Oid			roleid;
+ 	RoleAttr	attributes;
  
  	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
  	dsc = RelationGetDescr(rel);
*************** RenameRole(const char *oldname, const ch
*** 1173,1179 ****
  	/*
  	 * createrole is enough privilege unless you want to mess with a superuser
  	 */
! 	if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
  	{
  		if (!superuser())
  			ereport(ERROR,
--- 1208,1215 ----
  	/*
  	 * createrole is enough privilege unless you want to mess with a superuser
  	 */
! 	attributes = ((Form_pg_authid) GETSTRUCT(oldtuple))->rolattr;
! 	if ((attributes & ROLE_ATTR_SUPERUSER) > 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
new file mode 100644
index 6ce8dae..a8a9f2e
*** a/src/backend/commands/variable.c
--- b/src/backend/commands/variable.c
*************** check_session_authorization(char **newva
*** 776,781 ****
--- 776,782 ----
  	Oid			roleid;
  	bool		is_superuser;
  	role_auth_extra *myextra;
+ 	RoleAttr	attributes;
  
  	/* Do nothing for the boot_val default of NULL */
  	if (*newval == NULL)
*************** check_session_authorization(char **newva
*** 800,806 ****
  	}
  
  	roleid = HeapTupleGetOid(roleTup);
! 	is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
  
  	ReleaseSysCache(roleTup);
  
--- 801,808 ----
  	}
  
  	roleid = HeapTupleGetOid(roleTup);
! 	attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
! 	is_superuser = ((attributes & ROLE_ATTR_SUPERUSER) > 0);
  
  	ReleaseSysCache(roleTup);
  
*************** check_role(char **newval, void **extra,
*** 844,849 ****
--- 846,852 ----
  	Oid			roleid;
  	bool		is_superuser;
  	role_auth_extra *myextra;
+ 	RoleAttr	attributes;
  
  	if (strcmp(*newval, "none") == 0)
  	{
*************** check_role(char **newval, void **extra,
*** 872,878 ****
  		}
  
  		roleid = HeapTupleGetOid(roleTup);
! 		is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
  
  		ReleaseSysCache(roleTup);
  
--- 875,882 ----
  		}
  
  		roleid = HeapTupleGetOid(roleTup);
! 		attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
! 		is_superuser = ((attributes & ROLE_ATTR_SUPERUSER) > 0);
  
  		ReleaseSysCache(roleTup);
  
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
new file mode 100644
index dc6eb2c..f6d86ac
*** a/src/backend/utils/adt/acl.c
--- b/src/backend/utils/adt/acl.c
*************** static Oid	convert_type_name(text *typen
*** 115,120 ****
--- 115,121 ----
  static AclMode convert_type_priv_string(text *priv_type_text);
  static AclMode convert_role_priv_string(text *priv_type_text);
  static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
+ static RoleAttr convert_role_attr_string(text *attr_type_text);
  
  static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue);
  
*************** aclitemin(PG_FUNCTION_ARGS)
*** 577,582 ****
--- 578,584 ----
  	PG_RETURN_ACLITEM_P(aip);
  }
  
+ 
  /*
   * aclitemout
   *		Allocates storage for, and fills in, a new null-delimited string
*************** pg_role_aclcheck(Oid role_oid, Oid rolei
*** 4602,4607 ****
--- 4604,4712 ----
  	return ACLCHECK_NO_PRIV;
  }
  
+ /*
+  * has_role_attribute_id
+  *		Check the named role attribute on a role by given role oid.
+  */
+ Datum
+ has_role_attribute_id(PG_FUNCTION_ARGS)
+ {
+ 	Oid			roleoid = PG_GETARG_OID(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(role_has_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * get_all_role_attributes_attr
+  *		Convert a RoleAttr representation of role attributes into an array of
+  *		corresponding text values.
+  *
+  * The first and only argument is a RoleAttr (int64) representation of the
+  * role attributes.
+  */
+ Datum
+ get_all_role_attributes_rolattr(PG_FUNCTION_ARGS)
+ {
+ 	RoleAttr		attributes = PG_GETARG_INT64(0);
+ 	List		   *attribute_list = NIL;
+ 	ListCell	   *attribute;
+ 	Datum		   *temp_array;
+ 	ArrayType	   *result;
+ 	int				num_attributes;
+ 	int				i = 0;
+ 
+ 	/*
+ 	 * If no attributes are assigned, then there is no need to go through the
+ 	 * individual checks for which are assigned.  Therefore, we can short circuit
+ 	 * and return an empty array.
+ 	 */
+ 	if (attributes == ROLE_ATTR_NONE)
+ 		PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));
+ 
+ 	/* Determine which attributes are assigned. */
+ 	if ((attributes & ROLE_ATTR_SUPERUSER) > 0)
+ 		attribute_list = lappend(attribute_list, "Superuser");
+ 	if ((attributes & ROLE_ATTR_INHERIT) > 0)
+ 		attribute_list = lappend(attribute_list, "Inherit");
+ 	if ((attributes & ROLE_ATTR_CREATEROLE) > 0)
+ 		attribute_list = lappend(attribute_list, "Create Role");
+ 	if ((attributes & ROLE_ATTR_CREATEDB) > 0)
+ 		attribute_list = lappend(attribute_list, "Create DB");
+ 	if ((attributes & ROLE_ATTR_CATUPDATE) > 0)
+ 		attribute_list = lappend(attribute_list, "Catalog Update");
+ 	if ((attributes & ROLE_ATTR_CANLOGIN) > 0)
+ 		attribute_list = lappend(attribute_list, "Login");
+ 	if ((attributes & ROLE_ATTR_REPLICATION) > 0)
+ 		attribute_list = lappend(attribute_list, "Replication");
+ 	if ((attributes & ROLE_ATTR_BYPASSRLS) > 0)
+ 		attribute_list = lappend(attribute_list, "Bypass RLS");
+ 
+ 	/* Build and return result array */
+ 	num_attributes = list_length(attribute_list);
+ 	temp_array = (Datum *) palloc(num_attributes * sizeof(Datum));
+ 
+ 	foreach(attribute, attribute_list)
+ 		temp_array[i++] = CStringGetTextDatum(lfirst(attribute));
+ 
+ 	result = construct_array(temp_array, num_attributes, TEXTOID, -1, false, 'i');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * convert_role_attr_string
+  *		Convert text string to RoleAttr value.
+  */
+ static RoleAttr
+ convert_role_attr_string(text *attr_type_text)
+ {
+ 	char	   *attr_type = text_to_cstring(attr_type_text);
+ 
+ 	if (pg_strcasecmp(attr_type, "SUPERUSER") == 0)
+ 		return ROLE_ATTR_SUPERUSER;
+ 	else if (pg_strcasecmp(attr_type, "INHERIT") == 0)
+ 		return ROLE_ATTR_INHERIT;
+ 	else if (pg_strcasecmp(attr_type, "CREATEROLE") == 0)
+ 		return ROLE_ATTR_CREATEROLE;
+ 	else if (pg_strcasecmp(attr_type, "CREATEDB") == 0)
+ 		return ROLE_ATTR_CREATEDB;
+ 	else if (pg_strcasecmp(attr_type, "CATUPDATE") == 0)
+ 		return ROLE_ATTR_CATUPDATE;
+ 	else if (pg_strcasecmp(attr_type, "CANLOGIN") == 0)
+ 		return ROLE_ATTR_CANLOGIN;
+ 	else if (pg_strcasecmp(attr_type, "REPLICATION") == 0)
+ 		return ROLE_ATTR_REPLICATION;
+ 	else if (pg_strcasecmp(attr_type, "BYPASSRLS") == 0)
+ 		return ROLE_ATTR_BYPASSRLS;
+ 	else
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("unrecognized role attribute: \"%s\"", attr_type)));
+ }
  
  /*
   * initialization function (called by InitPostgres)
*************** RoleMembershipCacheCallback(Datum arg, i
*** 4638,4653 ****
  static bool
  has_rolinherit(Oid roleid)
  {
! 	bool		result = false;
  	HeapTuple	utup;
  
  	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(utup))
  	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
  		ReleaseSysCache(utup);
  	}
! 	return result;
  }
  
  
--- 4743,4758 ----
  static bool
  has_rolinherit(Oid roleid)
  {
! 	RoleAttr	attributes;
  	HeapTuple	utup;
  
  	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(utup))
  	{
! 		attributes = ((Form_pg_authid) GETSTRUCT(utup))->rolattr;
  		ReleaseSysCache(utup);
  	}
! 	return ((attributes & ROLE_ATTR_INHERIT) > 0);
  }
  
  
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
new file mode 100644
index 8fccb4c..9dd2dbc
*** a/src/backend/utils/init/miscinit.c
--- b/src/backend/utils/init/miscinit.c
***************
*** 40,45 ****
--- 40,46 ----
  #include "storage/pg_shmem.h"
  #include "storage/proc.h"
  #include "storage/procarray.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/guc.h"
  #include "utils/memutils.h"
*************** SetUserIdAndContext(Oid userid, bool sec
*** 334,349 ****
  bool
  has_rolreplication(Oid roleid)
  {
! 	bool		result = false;
  	HeapTuple	utup;
  
  	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(utup))
  	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication;
  		ReleaseSysCache(utup);
  	}
! 	return result;
  }
  
  /*
--- 335,350 ----
  bool
  has_rolreplication(Oid roleid)
  {
! 	RoleAttr	attributes;
  	HeapTuple	utup;
  
  	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(utup))
  	{
! 		attributes = ((Form_pg_authid) GETSTRUCT(utup))->rolattr;
  		ReleaseSysCache(utup);
  	}
! 	return ((attributes & ROLE_ATTR_REPLICATION) > 0);
  }
  
  /*
*************** InitializeSessionUserId(const char *role
*** 375,381 ****
  	roleid = HeapTupleGetOid(roleTup);
  
  	AuthenticatedUserId = roleid;
! 	AuthenticatedUserIsSuperuser = rform->rolsuper;
  
  	/* This sets OuterUserId/CurrentUserId too */
  	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
--- 376,382 ----
  	roleid = HeapTupleGetOid(roleTup);
  
  	AuthenticatedUserId = roleid;
! 	AuthenticatedUserIsSuperuser = ((rform->rolattr & ROLE_ATTR_SUPERUSER) > 0);
  
  	/* This sets OuterUserId/CurrentUserId too */
  	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
*************** InitializeSessionUserId(const char *role
*** 394,400 ****
  		/*
  		 * Is role allowed to login at all?
  		 */
! 		if (!rform->rolcanlogin)
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  					 errmsg("role \"%s\" is not permitted to log in",
--- 395,401 ----
  		/*
  		 * Is role allowed to login at all?
  		 */
! 		if (!((rform->rolattr & ROLE_ATTR_CANLOGIN) > 0))
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  					 errmsg("role \"%s\" is not permitted to log in",
diff --git a/src/backend/utils/misc/superuser.c b/src/backend/utils/misc/superuser.c
new file mode 100644
index ff0f947..43febad
*** a/src/backend/utils/misc/superuser.c
--- b/src/backend/utils/misc/superuser.c
***************
*** 22,27 ****
--- 22,28 ----
  
  #include "access/htup_details.h"
  #include "catalog/pg_authid.h"
+ #include "utils/acl.h"
  #include "utils/inval.h"
  #include "utils/syscache.h"
  #include "miscadmin.h"
*************** superuser_arg(Oid roleid)
*** 58,63 ****
--- 59,65 ----
  {
  	bool		result;
  	HeapTuple	rtup;
+ 	RoleAttr	attributes;
  
  	/* Quick out for cache hit */
  	if (OidIsValid(last_roleid) && last_roleid == roleid)
*************** superuser_arg(Oid roleid)
*** 71,77 ****
  	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(rtup))
  	{
! 		result = ((Form_pg_authid) GETSTRUCT(rtup))->rolsuper;
  		ReleaseSysCache(rtup);
  	}
  	else
--- 73,80 ----
  	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(rtup))
  	{
! 		attributes = ((Form_pg_authid) GETSTRUCT(rtup))->rolattr;
! 		result = ((attributes & ROLE_ATTR_SUPERUSER) > 0);
  		ReleaseSysCache(rtup);
  	}
  	else
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
new file mode 100644
index 3b63d2b..ff8f035
*** a/src/include/catalog/pg_authid.h
--- b/src/include/catalog/pg_authid.h
***************
*** 22,27 ****
--- 22,28 ----
  #define PG_AUTHID_H
  
  #include "catalog/genbki.h"
+ #include "nodes/parsenodes.h"
  
  /*
   * The CATALOG definition has to refer to the type of rolvaliduntil as
***************
*** 45,60 ****
  CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
  {
  	NameData	rolname;		/* name of role */
! 	bool		rolsuper;		/* read this field via superuser() only! */
! 	bool		rolinherit;		/* inherit privileges from other roles? */
! 	bool		rolcreaterole;	/* allowed to create more roles? */
! 	bool		rolcreatedb;	/* allowed to create databases? */
! 	bool		rolcatupdate;	/* allowed to alter catalogs manually? */
! 	bool		rolcanlogin;	/* allowed to log in as session user? */
! 	bool		rolreplication; /* role used for streaming replication */
! 	bool		rolbypassrls;	/* allowed to bypass row level security? */
  	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
- 
  	/* remaining fields may be null; use heap_getattr to read them! */
  	text		rolpassword;	/* password, if any */
  	timestamptz rolvaliduntil;	/* password expiration time, if any */
--- 46,53 ----
  CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
  {
  	NameData	rolname;		/* name of role */
! 	int64		rolattr;		/* role attribute bitmask */
  	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
  	/* remaining fields may be null; use heap_getattr to read them! */
  	text		rolpassword;	/* password, if any */
  	timestamptz rolvaliduntil;	/* password expiration time, if any */
*************** typedef FormData_pg_authid *Form_pg_auth
*** 74,101 ****
   *		compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid					12
  #define Anum_pg_authid_rolname			1
! #define Anum_pg_authid_rolsuper			2
! #define Anum_pg_authid_rolinherit		3
! #define Anum_pg_authid_rolcreaterole	4
! #define Anum_pg_authid_rolcreatedb		5
! #define Anum_pg_authid_rolcatupdate		6
! #define Anum_pg_authid_rolcanlogin		7
! #define Anum_pg_authid_rolreplication	8
! #define Anum_pg_authid_rolbypassrls		9
! #define Anum_pg_authid_rolconnlimit		10
! #define Anum_pg_authid_rolpassword		11
! #define Anum_pg_authid_rolvaliduntil	12
  
  /* ----------------
   *		initial contents of pg_authid
   *
   * The uppercase quantities will be replaced at initdb time with
   * user choices.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t -1 _null_ _null_));
  
  #define BOOTSTRAP_SUPERUSERID 10
  
--- 67,108 ----
   *		compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid					5
  #define Anum_pg_authid_rolname			1
! #define Anum_pg_authid_rolattr			2
! #define Anum_pg_authid_rolconnlimit		3
! #define Anum_pg_authid_rolpassword		4
! #define Anum_pg_authid_rolvaliduntil	5
! 
! /* ----------------
!  * Role attributes are encoded so that we can OR them together in a bitmask.
!  * The present representation of RoleAttr (defined in acl.h) limits us to 64
!  * distinct rights.
!  * ----------------
!  */
! #define ROLE_ATTR_SUPERUSER		(1<<0)
! #define ROLE_ATTR_INHERIT		(1<<1)
! #define ROLE_ATTR_CREATEROLE	(1<<2)
! #define ROLE_ATTR_CREATEDB		(1<<3)
! #define ROLE_ATTR_CATUPDATE		(1<<4)
! #define ROLE_ATTR_CANLOGIN		(1<<5)
! #define ROLE_ATTR_REPLICATION	(1<<6)
! #define ROLE_ATTR_BYPASSRLS		(1<<7)
! #define N_ROLE_ATTRIBUTES		8		/* 1 plus the last 1<<x */
! #define ROLE_ATTR_NONE			0
! #define ROLE_ATTR_ALL			255		/* All currently available attributes. */
  
  /* ----------------
   *		initial contents of pg_authid
   *
   * The uppercase quantities will be replaced at initdb time with
   * user choices.
+  *
+  * PGROLATTRALL is substituted by genbki.pl to use the value defined by
+  * ROLE_ATTR_ALL.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" PGROLATTRALL -1 _null_ _null_));
  
  #define BOOTSTRAP_SUPERUSERID 10
  
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index 56399ac..84c6bee
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("current user privilege on any col
*** 2682,2687 ****
--- 2682,2693 ----
  DATA(insert OID = 3029 (  has_any_column_privilege	   PGNSP PGUID 12 10 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ has_any_column_privilege_id _null_ _null_ _null_ ));
  DESCR("current user privilege on any column by rel oid");
  
+ DATA(insert OID = 6000 (  has_role_attribute		   PGNSP PGUID 12 10 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ has_role_attribute_id _null_ _null_ _null_));
+ DESCR("user role attribute by user oid");
+ 
+ DATA(insert OID = 6001 (  get_all_role_attributes	   PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 1009 "20" _null_ _null_ _null_ _null_ get_all_role_attributes_rolattr _null_ _null_ _null_));
+ DESCR("convert rolattr to string array");
+ 
  DATA(insert OID = 1928 (  pg_stat_get_numscans			PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 20 "26" _null_ _null_ _null_ _null_ pg_stat_get_numscans _null_ _null_ _null_ ));
  DESCR("statistics: number of scans done for table/index");
  DATA(insert OID = 1929 (  pg_stat_get_tuples_returned	PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 20 "26" _null_ _null_ _null_ _null_ pg_stat_get_tuples_returned _null_ _null_ _null_ ));
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 255415d..d59dcb4
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef uint32 AclMode;			/* a bitmask o
*** 78,83 ****
--- 78,101 ----
  /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
  #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
  
+ /*
+  * Role attributes are encoded so that we can OR them together in a bitmask.
+  * The present representation of RoleAttr limits us to 64 distinct rights.
+  *
+  * Caution: changing these codes breaks stored RoleAttrs, hence forces initdb.
+  */
+ typedef uint64 RoleAttr;		/* a bitmask for role attribute bits */
+ 
+ #define ROLE_ATTR_SUPERUSER		(1<<0)
+ #define ROLE_ATTR_INHERIT		(1<<1)
+ #define ROLE_ATTR_CREATEROLE	(1<<2)
+ #define ROLE_ATTR_CREATEDB		(1<<3)
+ #define ROLE_ATTR_CATUPDATE		(1<<4)
+ #define ROLE_ATTR_CANLOGIN		(1<<5)
+ #define ROLE_ATTR_REPLICATION	(1<<6)
+ #define ROLE_ATTR_BYPASSRLS		(1<<7)
+ #define N_ROLE_ATTRIBUTES		8
+ #define ROLE_ATTR_NONE			0
  
  /*****************************************************************************
   *	Query Tree
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
new file mode 100644
index a8e3164..1460e94
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
*************** typedef enum AclObjectKind
*** 200,205 ****
--- 200,206 ----
  	MAX_ACL_KIND				/* MUST BE LAST */
  } AclObjectKind;
  
+ typedef uint64 RoleAttr;		/* a bitmask for role attribute bits */
  
  /*
   * routines used internally
*************** extern bool pg_event_trigger_ownercheck(
*** 328,332 ****
--- 329,334 ----
  extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
  extern bool has_createrole_privilege(Oid roleid);
  extern bool has_bypassrls_privilege(Oid roleid);
+ extern bool role_has_attribute(Oid roleid, RoleAttr capability);
  
  #endif   /* ACL_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index 417fd17..7efee31
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum pg_has_role_id_name(PG_FUNC
*** 106,111 ****
--- 106,113 ----
  extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS);
  extern Datum pg_has_role_name(PG_FUNCTION_ARGS);
  extern Datum pg_has_role_id(PG_FUNCTION_ARGS);
+ extern Datum has_role_attribute_id(PG_FUNCTION_ARGS);
+ extern Datum get_all_role_attributes_rolattr(PG_FUNCTION_ARGS);
  
  /* bool.c */
  extern Datum boolin(PG_FUNCTION_ARGS);
#13Stephen Frost
sfrost@snowman.net
In reply to: Adam Brightwell (#12)
Re: Role Attribute Bitmask Catalog Representation

Adam,

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

Please let me know what you think, all feedback is greatly appreciated.

Thanks for this. Found time to do more of a review and have a few
comments:

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
new file mode 100644
index d30612c..93eb2e6
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
*************** pg_extension_ownercheck(Oid ext_oid, Oid
*** 5051,5056 ****
--- 5031,5058 ----
}
/*
+  * Check whether the specified role has a specific role attribute.
+  */
+ bool
+ role_has_attribute(Oid roleid, RoleAttr attribute)
+ {
+ 	RoleAttr	attributes;
+ 	HeapTuple	tuple;
+ 
+ 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
+ 
+ 	if (!HeapTupleIsValid(tuple))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("role with OID %u does not exist", roleid)));
+ 
+ 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
+ 	ReleaseSysCache(tuple);
+ 
+ 	return ((attributes & attribute) > 0);
+ }

I'd put the superuser_arg() check in role_has_attribute.

--- 5066,5089 ----
bool
has_createrole_privilege(Oid roleid)
{
/* Superusers bypass all permission checking. */
if (superuser_arg(roleid))
return true;

! return role_has_attribute(roleid, ROLE_ATTR_CREATEROLE);
}

And then ditch the individual has_X_privilege() calls (I note that you
didn't appear to add back the has_rolcatupdate() function..).

diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
new file mode 100644
index 1a73fd8..72c5dcc
*** a/src/backend/commands/user.c
--- b/src/backend/commands/user.c
*************** have_createrole_privilege(void)
*** 63,68 ****
--- 63,73 ----
return has_createrole_privilege(GetUserId());
}
+ static RoleAttr
+ set_role_attribute(RoleAttr attributes, RoleAttr attribute)
+ {
+ 	return ((attributes & ~(0xFFFFFFFFFFFFFFFF)) | attribute);
+ }

I don't think this is doing what you think it is..? It certainly isn't
working as expected by AlterRole(). Rather than using a function for
these relatively simple operations, why not just have AlterRole() do:

if (isX >= 0)
attributes = (isX > 0) ? attributes | ROLE_ATTR_X
: attributes & ~(ROLE_ATTR_X);

Otherwise, you'd probably need to pass a flag into set_role_attribute()
to indicate if you're setting or unsetting the bit, or have an
'unset_role_attribute()' function, but all of that seems like overkill..

*************** CreateRole(CreateRoleStmt *stmt)
--- 86,99 ----
char	   *password = NULL;	/* user password */
bool		encrypt_password = Password_encryption; /* encrypt password? */
char		encrypted_password[MD5_PASSWD_LEN + 1];
! 	bool		issuper = false;		/* Make the user a superuser? */
! 	bool		inherit = true;			/* Auto inherit privileges? */

I'd probably leave the whitespace-only changes out.

*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 889,895 ****
* To mess with a superuser you gotta be superuser; else you need
* createrole, or just want to change your own settings
*/
! 		if (((Form_pg_authid) GETSTRUCT(roletuple))->rolsuper)
{
if (!superuser())
ereport(ERROR,
--- 921,928 ----
* To mess with a superuser you gotta be superuser; else you need
* createrole, or just want to change your own settings
*/
! 		attributes = ((Form_pg_authid) GETSTRUCT(roletuple))->rolattr;
! 		if ((attributes & ROLE_ATTR_SUPERUSER) > 0)
{
if (!superuser())
ereport(ERROR,

We don't bother with the '> 0' in any of the existing bit testing that
goes on in the backend, so I don't think it makes sense to start now.
Just do

if (attributes & ROLE_ATTR_SUPERUSER) ... etc

and be done with it.

*************** aclitemin(PG_FUNCTION_ARGS)
*** 577,582 ****
--- 578,584 ----
PG_RETURN_ACLITEM_P(aip);
}

+
/*
* aclitemout
* Allocates storage for, and fills in, a new null-delimited string

More whitespace changes... :/

*************** pg_role_aclcheck(Oid role_oid, Oid rolei
*** 4602,4607 ****
--- 4604,4712 ----
return ACLCHECK_NO_PRIV;
}
+ /*
+  * has_role_attribute_id
+  *		Check the named role attribute on a role by given role oid.
+  */
+ Datum
+ has_role_attribute_id(PG_FUNCTION_ARGS)
+ {
+ 	Oid			roleoid = PG_GETARG_OID(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(role_has_attribute(roleoid, attribute));
+ }

Why not have this as 'pg_has_role_id_attribute' and then have a
'pg_has_role_name_attribute' also? Seems like going with the pg_
namespace is the direction we want to go in, though I'm not inclined to
argue about it if folks prefer has_X.

+ /*
+  * get_all_role_attributes_attr
+  *		Convert a RoleAttr representation of role attributes into an array of
+  *		corresponding text values.
+  *
+  * The first and only argument is a RoleAttr (int64) representation of the
+  * role attributes.
+  */
+ Datum
+ get_all_role_attributes_rolattr(PG_FUNCTION_ARGS)

The function name in the comment should match the function..

+ {
+ 	RoleAttr		attributes = PG_GETARG_INT64(0);
+ 	List		   *attribute_list = NIL;
+ 	ListCell	   *attribute;
+ 	Datum		   *temp_array;
+ 	ArrayType	   *result;
+ 	int				num_attributes;
+ 	int				i = 0;
+ 
+ 	/*
+ 	 * If no attributes are assigned, then there is no need to go through the
+ 	 * individual checks for which are assigned.  Therefore, we can short circuit
+ 	 * and return an empty array.
+ 	 */
+ 	if (attributes == ROLE_ATTR_NONE)
+ 		PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));
+ 
+ 	/* Determine which attributes are assigned. */
+ 	if ((attributes & ROLE_ATTR_SUPERUSER) > 0)
+ 		attribute_list = lappend(attribute_list, "Superuser");
+ 	if ((attributes & ROLE_ATTR_INHERIT) > 0)
+ 		attribute_list = lappend(attribute_list, "Inherit");
+ 	if ((attributes & ROLE_ATTR_CREATEROLE) > 0)
+ 		attribute_list = lappend(attribute_list, "Create Role");
+ 	if ((attributes & ROLE_ATTR_CREATEDB) > 0)
+ 		attribute_list = lappend(attribute_list, "Create DB");
+ 	if ((attributes & ROLE_ATTR_CATUPDATE) > 0)
+ 		attribute_list = lappend(attribute_list, "Catalog Update");
+ 	if ((attributes & ROLE_ATTR_CANLOGIN) > 0)
+ 		attribute_list = lappend(attribute_list, "Login");
+ 	if ((attributes & ROLE_ATTR_REPLICATION) > 0)
+ 		attribute_list = lappend(attribute_list, "Replication");
+ 	if ((attributes & ROLE_ATTR_BYPASSRLS) > 0)
+ 		attribute_list = lappend(attribute_list, "Bypass RLS");
+ 
+ 	/* Build and return result array */
+ 	num_attributes = list_length(attribute_list);
+ 	temp_array = (Datum *) palloc(num_attributes * sizeof(Datum));
+ 
+ 	foreach(attribute, attribute_list)
+ 		temp_array[i++] = CStringGetTextDatum(lfirst(attribute));
+ 
+ 	result = construct_array(temp_array, num_attributes, TEXTOID, -1, false, 'i');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }

Seems like you could just make temp_array be N_ROLE_ATTRIBUTES in
size, instead of building a list, counting it, and then building the
array from that..?

*************** RoleMembershipCacheCallback(Datum arg, i
--- 4743,4758 ----
static bool
has_rolinherit(Oid roleid)
{
! 	RoleAttr	attributes;
HeapTuple	utup;

utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
if (HeapTupleIsValid(utup))
{
! attributes = ((Form_pg_authid) GETSTRUCT(utup))->rolattr;
ReleaseSysCache(utup);
}
! return ((attributes & ROLE_ATTR_INHERIT) > 0);
}

Do we really need to keep has_rolinherit for any reason..? Seems
unnecessary given it's only got one call site and it's nearly the same
as a call to role_has_attribute() anyway. Ditto with
has_rolreplication().

diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
new file mode 100644
index 3b63d2b..ff8f035
*** a/src/include/catalog/pg_authid.h
--- b/src/include/catalog/pg_authid.h
***************
*** 22,27 ****
--- 22,28 ----
#define PG_AUTHID_H

#include "catalog/genbki.h"
+ #include "nodes/parsenodes.h"

/*
* The CATALOG definition has to refer to the type of rolvaliduntil as

Thought we were getting rid of this..?

! #define N_ROLE_ATTRIBUTES 8 /* 1 plus the last 1<<x */
! #define ROLE_ATTR_NONE 0
! #define ROLE_ATTR_ALL 255 /* All currently available attributes. */

Or:

#define ROLE_ATTR_ALL (1<<N_ROLE_ATTRIBUTES)-1

?

/* ----------------
*		initial contents of pg_authid
*
* The uppercase quantities will be replaced at initdb time with
* user choices.
+  *
+  * PGROLATTRALL is substituted by genbki.pl to use the value defined by
+  * ROLE_ATTR_ALL.
* ----------------
*/
! DATA(insert OID = 10 ( "POSTGRES" PGROLATTRALL -1 _null_ _null_));

#define BOOTSTRAP_SUPERUSERID 10

Is it actually necessary to do this substitution when the value is
#define'd in the same .h file...? Might be something to check, if you
haven't already.

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 255415d..d59dcb4
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef uint32 AclMode;			/* a bitmask o
*** 78,83 ****
--- 78,101 ----
/* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
#define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
+ /*
+  * Role attributes are encoded so that we can OR them together in a bitmask.
+  * The present representation of RoleAttr limits us to 64 distinct rights.
+  *
+  * Caution: changing these codes breaks stored RoleAttrs, hence forces initdb.
+  */
+ typedef uint64 RoleAttr;		/* a bitmask for role attribute bits */
+ 
+ #define ROLE_ATTR_SUPERUSER		(1<<0)
+ #define ROLE_ATTR_INHERIT		(1<<1)
+ #define ROLE_ATTR_CREATEROLE	(1<<2)
+ #define ROLE_ATTR_CREATEDB		(1<<3)
+ #define ROLE_ATTR_CATUPDATE		(1<<4)
+ #define ROLE_ATTR_CANLOGIN		(1<<5)
+ #define ROLE_ATTR_REPLICATION	(1<<6)
+ #define ROLE_ATTR_BYPASSRLS		(1<<7)
+ #define N_ROLE_ATTRIBUTES		8
+ #define ROLE_ATTR_NONE			0

These shouldn't need to be here any more..?

Thanks!

Stephen

#14Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Stephen Frost (#13)
Re: Role Attribute Bitmask Catalog Representation

Stephen,

Thanks for this. Found time to do more of a review and have a few

comments:

Thanks for taking a look at this and for the feedback.

I'd put the superuser_arg() check in role_has_attribute.

Ok. Though, this would affect how CATUPDATE is handled. Peter Eisentraut
previously raised a question about whether superuser checks should be
included with catupdate which led me to create the following post.

/messages/by-id/CAKRt6CQOvT2KiyKg2gFf7h9k8+JVU1149zLb0EXTkKk7TAQGMQ@mail.gmail.com

Certainly, we could keep has_rolcatupdate for this case and put the
superuser check in role_has_attribute, but it seems like it might be worth
taking a look at whether a superuser can bypass catupdate or not. Just a
thought.

And then ditch the individual has_X_privilege() calls (I note that you

didn't appear to add back the has_rolcatupdate() function..).

Ok. I had originally thought for this patch that I would try to minimize
these types of changes, though perhaps this is that opportunity previously
mentioned in refactoring those. However, the catupdate question still
remains.

+ static RoleAttr

+ set_role_attribute(RoleAttr attributes, RoleAttr attribute)
+ {
+     return ((attributes & ~(0xFFFFFFFFFFFFFFFF)) | attribute);
+ }

I don't think this is doing what you think it is..? It certainly isn't
working as expected by AlterRole(). Rather than using a function for
these relatively simple operations, why not just have AlterRole() do:

if (isX >= 0)
attributes = (isX > 0) ? attributes | ROLE_ATTR_X
: attributes &
~(ROLE_ATTR_X);

Yes, this was originally my first approach. I'm not recollecting why I
decided on the other route, but agree that is preferable and simpler.

Otherwise, you'd probably need to pass a flag into set_role_attribute()
to indicate if you're setting or unsetting the bit, or have an
'unset_role_attribute()' function, but all of that seems like overkill..

Agreed.

We don't bother with the '> 0' in any of the existing bit testing that

goes on in the backend, so I don't think it makes sense to start now.
Just do

if (attributes & ROLE_ATTR_SUPERUSER) ... etc

and be done with it.

Ok, easy enough.

Why not have this as 'pg_has_role_id_attribute' and then have a

'pg_has_role_name_attribute' also? Seems like going with the pg_
namespace is the direction we want to go in, though I'm not inclined to
argue about it if folks prefer has_X.

I have no reason for one over the other, though I did ask myself that
question. I did find it curious that in some cases there is "has_X" and
then in others "pg_has_X". Perhaps I'm not looking in the right places,
but I haven't found anything that helps to distinguish when one vs the
other is appropriate (even if it is a general rule of thumb).

Seems like you could just make temp_array be N_ROLE_ATTRIBUTES in

size, instead of building a list, counting it, and then building the
array from that..?

Yes, this is true.

Do we really need to keep has_rolinherit for any reason..? Seems

unnecessary given it's only got one call site and it's nearly the same
as a call to role_has_attribute() anyway. Ditto with
has_rolreplication().

I really don't see any reason and as above, I can certainly do those
refactors now if that is what is desired.

Thought we were getting rid of this..?

! #define N_ROLE_ATTRIBUTES 8 /* 1 plus the last

1<<x */

! #define ROLE_ATTR_NONE 0
! #define ROLE_ATTR_ALL 255 /* All

currently available attributes. */

Or:

#define ROLE_ATTR_ALL (1<<N_ROLE_ATTRIBUTES)-1

Yes, we were, however the latter causes a syntax error with initdb. :-/

! DATA(insert OID = 10 ( "POSTGRES" PGROLATTRALL -1 _null_ _null_));

#define BOOTSTRAP_SUPERUSERID 10

Is it actually necessary to do this substitution when the value is
#define'd in the same .h file...? Might be something to check, if you
haven't already.

Yes, I had previously checked this, I get the following error from initdb.

FATAL: invalid input syntax for integer: "ROLE_ATTR_ALL"

+ #define ROLE_ATTR_SUPERUSER (1<<0)

+ #define ROLE_ATTR_INHERIT           (1<<1)
+ #define ROLE_ATTR_CREATEROLE        (1<<2)
+ #define ROLE_ATTR_CREATEDB          (1<<3)
+ #define ROLE_ATTR_CATUPDATE         (1<<4)
+ #define ROLE_ATTR_CANLOGIN          (1<<5)
+ #define ROLE_ATTR_REPLICATION       (1<<6)
+ #define ROLE_ATTR_BYPASSRLS         (1<<7)
+ #define N_ROLE_ATTRIBUTES           8
+ #define ROLE_ATTR_NONE                      0

These shouldn't need to be here any more..?

No they shouldn't, not sure how I missed that.

Thanks,
Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

#15Stephen Frost
sfrost@snowman.net
In reply to: Adam Brightwell (#14)
Re: Role Attribute Bitmask Catalog Representation

Adam,

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

Ok. Though, this would affect how CATUPDATE is handled. Peter Eisentraut
previously raised a question about whether superuser checks should be
included with catupdate which led me to create the following post.

/messages/by-id/CAKRt6CQOvT2KiyKg2gFf7h9k8+JVU1149zLb0EXTkKk7TAQGMQ@mail.gmail.com

Certainly, we could keep has_rolcatupdate for this case and put the
superuser check in role_has_attribute, but it seems like it might be worth
taking a look at whether a superuser can bypass catupdate or not. Just a
thought.

My recollection matches the documentation- rolcatupdate should be
required to update the catalogs. The fact that rolcatupdate is set by
AlterUser to match rolsuper is an interesting point and one which we
might want to reconsider, but that's beyond the scope of this patch.

Ergo, I'd suggest keeping has_rolcatupdate, but have it do the check
itself directly instead of calling down into role_has_attribute().

There's an interesting flip side to that though, which is the question
of what to do with pg_roles and psql. Based on the discussion this far,
it seems like we'd want to keep the distinction for pg_roles and psql
based on what bits have explicitly been set rather than what's actually
checked for. As such, I'd have one other function-
check_has_attribute() which *doesn't* have the superuser allow-all check
and is what is used in pg_roles and by psql. I'd expose both functions
at the SQL level.

Ok. I had originally thought for this patch that I would try to minimize
these types of changes, though perhaps this is that opportunity previously
mentioned in refactoring those. However, the catupdate question still
remains.

It makes sense to me, at least, to include removing those individual
attribute functions in this patch.

I have no reason for one over the other, though I did ask myself that
question. I did find it curious that in some cases there is "has_X" and
then in others "pg_has_X". Perhaps I'm not looking in the right places,
but I haven't found anything that helps to distinguish when one vs the
other is appropriate (even if it is a general rule of thumb).

Given that we're changing things anyway, it seems to me that the pg_
prefix makes sense.

Yes, we were, however the latter causes a syntax error with initdb. :-/

Ok, then just stuff the 255 back there and add a comment about why it's
required and mention that cute tricks to calculate the value won't work.

Thanks!

Stephen

#16Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Stephen Frost (#15)
1 attachment(s)
Re: Role Attribute Bitmask Catalog Representation

All,

I have attached an updated patch that incorporates the feedback and
recommendations provided.

Thanks,
Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

Attachments:

role-attribute-bitmask-v3.patchtext/x-patch; charset=US-ASCII; name=role-attribute-bitmask-v3.patchDownload
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
new file mode 100644
index 133143d..ccdff09
*** a/src/backend/access/transam/xlogfuncs.c
--- b/src/backend/access/transam/xlogfuncs.c
***************
*** 22,32 ****
--- 22,34 ----
  #include "access/xlog_internal.h"
  #include "access/xlogutils.h"
  #include "catalog/catalog.h"
+ #include "catalog/pg_authid.h"
  #include "catalog/pg_type.h"
  #include "funcapi.h"
  #include "miscadmin.h"
  #include "replication/walreceiver.h"
  #include "storage/smgr.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/numeric.h"
  #include "utils/guc.h"
*************** pg_start_backup(PG_FUNCTION_ARGS)
*** 54,60 ****
  
  	backupidstr = text_to_cstring(backupid);
  
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		   errmsg("must be superuser or replication role to run a backup")));
--- 56,62 ----
  
  	backupidstr = text_to_cstring(backupid);
  
! 	if (!superuser() && !check_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		   errmsg("must be superuser or replication role to run a backup")));
*************** pg_stop_backup(PG_FUNCTION_ARGS)
*** 82,88 ****
  {
  	XLogRecPtr	stoppoint;
  
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		 (errmsg("must be superuser or replication role to run a backup"))));
--- 84,90 ----
  {
  	XLogRecPtr	stoppoint;
  
! 	if (!superuser() && !check_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		 (errmsg("must be superuser or replication role to run a backup"))));
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
new file mode 100644
index d30612c..0b23ba0
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
*************** aclcheck_error_type(AclResult aclerr, Oi
*** 3423,3448 ****
  }
  
  
- /* Check if given user has rolcatupdate privilege according to pg_authid */
- static bool
- has_rolcatupdate(Oid roleid)
- {
- 	bool		rolcatupdate;
- 	HeapTuple	tuple;
- 
- 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (!HeapTupleIsValid(tuple))
- 		ereport(ERROR,
- 				(errcode(ERRCODE_UNDEFINED_OBJECT),
- 				 errmsg("role with OID %u does not exist", roleid)));
- 
- 	rolcatupdate = ((Form_pg_authid) GETSTRUCT(tuple))->rolcatupdate;
- 
- 	ReleaseSysCache(tuple);
- 
- 	return rolcatupdate;
- }
- 
  /*
   * Relay for the various pg_*_mask routines depending on object kind
   */
--- 3423,3428 ----
*************** pg_class_aclmask(Oid table_oid, Oid role
*** 3630,3636 ****
  	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
  		IsSystemClass(table_oid, classForm) &&
  		classForm->relkind != RELKIND_VIEW &&
! 		!has_rolcatupdate(roleid) &&
  		!allowSystemTableMods)
  	{
  #ifdef ACLDEBUG
--- 3610,3616 ----
  	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
  		IsSystemClass(table_oid, classForm) &&
  		classForm->relkind != RELKIND_VIEW &&
! 		!has_role_attribute(roleid, ROLE_ATTR_CATUPDATE) &&
  		!allowSystemTableMods)
  	{
  #ifdef ACLDEBUG
*************** pg_extension_ownercheck(Oid ext_oid, Oid
*** 5051,5102 ****
  }
  
  /*
!  * Check whether specified role has CREATEROLE privilege (or is a superuser)
   *
!  * Note: roles do not have owners per se; instead we use this test in
!  * places where an ownership-like permissions test is needed for a role.
!  * Be sure to apply it to the role trying to do the operation, not the
!  * role being operated on!	Also note that this generally should not be
!  * considered enough privilege if the target role is a superuser.
!  * (We don't handle that consideration here because we want to give a
!  * separate error message for such cases, so the caller has to deal with it.)
   */
  bool
! has_createrole_privilege(Oid roleid)
  {
! 	bool		result = false;
! 	HeapTuple	utup;
! 
! 	/* Superusers bypass all permission checking. */
! 	if (superuser_arg(roleid))
  		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  bool
! has_bypassrls_privilege(Oid roleid)
  {
! 	bool		result = false;
! 	HeapTuple	utup;
  
! 	/* Superusers bypass all permission checking. */
! 	if (superuser_arg(roleid))
! 		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolbypassrls;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  /*
--- 5031,5081 ----
  }
  
  /*
!  * has_role_attribute
!  *   Check if the role has the specified role has a specific role attribute.
!  *   This function will always return true for roles with superuser privileges
!  *   unless the attribute being checked is CATUPDATE.
   *
!  * roleid - the oid of the role to check.
!  * attribute - the attribute to check.
   */
  bool
! has_role_attribute(Oid roleid, RoleAttr attribute)
  {
! 	/*
! 	 * Superusers bypass all permission checking except in the case of CATUPDATE.
! 	 */
! 	if (!(attribute & ROLE_ATTR_CATUPDATE) && superuser_arg(roleid))
  		return true;
  
! 	return check_role_attribute(roleid, attribute);
  }
  
+ /*
+  * check_role_attribute
+  *   Check if the role with the specified id has been assigned a specific
+  *   role attribute.  This function does not allow any superuser bypass.
+  *
+  * roleid - the oid of the role to check.
+  * attribute - the attribute to check.
+  */
  bool
! check_role_attribute(Oid roleid, RoleAttr attribute)
  {
! 	RoleAttr	attributes;
! 	HeapTuple	tuple;
  
! 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  
! 	if (!HeapTupleIsValid(tuple))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("role with OID %u does not exist", roleid)));
! 
! 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 	ReleaseSysCache(tuple);
! 
! 	return (attributes & attribute);
  }
  
  /*
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
new file mode 100644
index ca89879..2929b66
*** a/src/backend/catalog/genbki.pl
--- b/src/backend/catalog/genbki.pl
*************** my $BOOTSTRAP_SUPERUSERID =
*** 90,95 ****
--- 90,97 ----
    find_defined_symbol('pg_authid.h', 'BOOTSTRAP_SUPERUSERID');
  my $PG_CATALOG_NAMESPACE =
    find_defined_symbol('pg_namespace.h', 'PG_CATALOG_NAMESPACE');
+ my $ROLE_ATTR_ALL =
+   find_defined_symbol('pg_authid.h', 'ROLE_ATTR_ALL');
  
  # Read all the input header files into internal data structures
  my $catalogs = Catalog::Catalogs(@input_files);
*************** foreach my $catname (@{ $catalogs->{name
*** 144,149 ****
--- 146,152 ----
  			# substitute constant values we acquired above
  			$row->{bki_values} =~ s/\bPGUID\b/$BOOTSTRAP_SUPERUSERID/g;
  			$row->{bki_values} =~ s/\bPGNSP\b/$PG_CATALOG_NAMESPACE/g;
+ 			$row->{bki_values} =~ s/\bPGROLATTRALL/$ROLE_ATTR_ALL/g;
  
  			# Save pg_type info for pg_attribute processing below
  			if ($catname eq 'pg_type')
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
new file mode 100644
index a036c62..79a16b1
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
*************** CREATE VIEW user_mapping_options AS
*** 2884,2890 ****
             CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
             CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                         OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
!                        OR (SELECT rolsuper FROM pg_authid WHERE rolname = current_user) THEN (pg_options_to_table(um.umoptions)).option_value
                       ELSE NULL END AS character_data) AS option_value
      FROM _pg_user_mappings um;
  
--- 2884,2895 ----
             CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
             CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                         OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
!                        OR (
!                             SELECT pg_check_role_attribute(pg_authid.oid, 'SUPERUSER') AS rolsuper
!                             FROM pg_authid
!                             WHERE rolname = current_user
!                           )
!                        THEN (pg_options_to_table(um.umoptions)).option_value
                       ELSE NULL END AS character_data) AS option_value
      FROM _pg_user_mappings um;
  
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
new file mode 100644
index e261307..6df7e4d
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
*************** check_object_ownership(Oid roleid, Objec
*** 1310,1316 ****
  			}
  			else
  			{
! 				if (!has_createrole_privilege(roleid))
  					ereport(ERROR,
  							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  							 errmsg("must have CREATEROLE privilege")));
--- 1310,1316 ----
  			}
  			else
  			{
! 				if (!has_role_attribute(roleid, ROLE_ATTR_CREATEROLE))
  					ereport(ERROR,
  							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  							 errmsg("must have CREATEROLE privilege")));
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
new file mode 100644
index 22b8cee..cb0e193
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 9,25 ****
  CREATE VIEW pg_roles AS
      SELECT
          rolname,
!         rolsuper,
!         rolinherit,
!         rolcreaterole,
!         rolcreatedb,
!         rolcatupdate,
!         rolcanlogin,
!         rolreplication,
          rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
-         rolbypassrls,
          setconfig as rolconfig,
          pg_authid.oid
      FROM pg_authid LEFT JOIN pg_db_role_setting s
--- 9,25 ----
  CREATE VIEW pg_roles AS
      SELECT
          rolname,
!         pg_check_role_attribute(pg_authid.oid, 'SUPERUSER') AS rolsuper,
!         pg_check_role_attribute(pg_authid.oid, 'INHERIT') AS rolinherit,
!         pg_check_role_attribute(pg_authid.oid, 'CREATEROLE') AS rolcreaterole,
!         pg_check_role_attribute(pg_authid.oid, 'CREATEDB') AS rolcreatedb,
!         pg_check_role_attribute(pg_authid.oid, 'CATUPDATE') AS rolcatupdate,
!         pg_check_role_attribute(pg_authid.oid, 'CANLOGIN') AS rolcanlogin,
!         pg_check_role_attribute(pg_authid.oid, 'REPLICATION') AS rolreplication,
!         pg_check_role_attribute(pg_authid.oid, 'BYPASSRLS') AS rolbypassrls,
          rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
          setconfig as rolconfig,
          pg_authid.oid
      FROM pg_authid LEFT JOIN pg_db_role_setting s
*************** CREATE VIEW pg_shadow AS
*** 29,44 ****
      SELECT
          rolname AS usename,
          pg_authid.oid AS usesysid,
!         rolcreatedb AS usecreatedb,
!         rolsuper AS usesuper,
!         rolcatupdate AS usecatupd,
!         rolreplication AS userepl,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          setconfig AS useconfig
      FROM pg_authid LEFT JOIN pg_db_role_setting s
      ON (pg_authid.oid = setrole AND setdatabase = 0)
!     WHERE rolcanlogin;
  
  REVOKE ALL on pg_shadow FROM public;
  
--- 29,44 ----
      SELECT
          rolname AS usename,
          pg_authid.oid AS usesysid,
!         pg_check_role_attribute(pg_authid.oid, 'CREATEDB') AS usecreatedb,
!         pg_check_role_attribute(pg_authid.oid, 'SUPERUSER') AS usesuper,
!         pg_check_role_attribute(pg_authid.oid, 'CATUPDATE') AS usecatupd,
!         pg_check_role_attribute(pg_authid.oid, 'REPLICATION') AS userepl,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          setconfig AS useconfig
      FROM pg_authid LEFT JOIN pg_db_role_setting s
      ON (pg_authid.oid = setrole AND setdatabase = 0)
!     WHERE pg_check_role_attribute(pg_authid.oid, 'CANLOGIN');
  
  REVOKE ALL on pg_shadow FROM public;
  
*************** CREATE VIEW pg_group AS
*** 48,54 ****
          oid AS grosysid,
          ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
      FROM pg_authid
!     WHERE NOT rolcanlogin;
  
  CREATE VIEW pg_user AS
      SELECT
--- 48,54 ----
          oid AS grosysid,
          ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
      FROM pg_authid
!     WHERE NOT pg_check_role_attribute(pg_authid.oid, 'CANLOGIN');
  
  CREATE VIEW pg_user AS
      SELECT
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
new file mode 100644
index 1a5244c..955dd28
*** a/src/backend/commands/dbcommands.c
--- b/src/backend/commands/dbcommands.c
*************** static bool get_db_info(const char *name
*** 85,91 ****
  			Oid *dbLastSysOidP, TransactionId *dbFrozenXidP,
  			MultiXactId *dbMinMultiP,
  			Oid *dbTablespace, char **dbCollate, char **dbCtype);
- static bool have_createdb_privilege(void);
  static void remove_dbtablespaces(Oid db_id);
  static bool check_db_file_conflict(Oid db_id);
  static int	errdetail_busy_db(int notherbackends, int npreparedxacts);
--- 85,90 ----
*************** createdb(const CreatedbStmt *stmt)
*** 291,297 ****
  	 * "giveaway" attacks.  Note that a superuser will always have both of
  	 * these privileges a fortiori.
  	 */
! 	if (!have_createdb_privilege())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to create database")));
--- 290,296 ----
  	 * "giveaway" attacks.  Note that a superuser will always have both of
  	 * these privileges a fortiori.
  	 */
! 	if (!has_role_attribute(GetUserId(), ROLE_ATTR_CREATEDB))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to create database")));
*************** RenameDatabase(const char *oldname, cons
*** 965,971 ****
  					   oldname);
  
  	/* must have createdb rights */
! 	if (!have_createdb_privilege())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to rename database")));
--- 964,970 ----
  					   oldname);
  
  	/* must have createdb rights */
! 	if (!has_role_attribute(GetUserId(), ROLE_ATTR_CREATEDB))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to rename database")));
*************** AlterDatabaseOwner(const char *dbname, O
*** 1623,1629 ****
  		 * databases.  Because superusers will always have this right, we need
  		 * no special case for them.
  		 */
! 		if (!have_createdb_privilege())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				   errmsg("permission denied to change owner of database")));
--- 1622,1628 ----
  		 * databases.  Because superusers will always have this right, we need
  		 * no special case for them.
  		 */
! 		if (!has_role_attribute(GetUserId(), ROLE_ATTR_CREATEDB))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				   errmsg("permission denied to change owner of database")));
*************** get_db_info(const char *name, LOCKMODE l
*** 1802,1827 ****
  	return result;
  }
  
- /* Check if current user has createdb privileges */
- static bool
- have_createdb_privilege(void)
- {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
- 	/* Superusers can always do everything */
- 	if (superuser())
- 		return true;
- 
- 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId()));
- 	if (HeapTupleIsValid(utup))
- 	{
- 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreatedb;
- 		ReleaseSysCache(utup);
- 	}
- 	return result;
- }
- 
  /*
   * Remove tablespace directories
   *
--- 1801,1806 ----
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
new file mode 100644
index 1a73fd8..6092776
*** a/src/backend/commands/user.c
--- b/src/backend/commands/user.c
*************** static void DelRoleMems(const char *role
*** 56,69 ****
  			bool admin_opt);
  
  
- /* Check if current user has createrole privileges */
- static bool
- have_createrole_privilege(void)
- {
- 	return has_createrole_privilege(GetUserId());
- }
- 
- 
  /*
   * CREATE ROLE
   */
--- 56,61 ----
*************** CreateRole(CreateRoleStmt *stmt)
*** 82,93 ****
  	bool		encrypt_password = Password_encryption; /* encrypt password? */
  	char		encrypted_password[MD5_PASSWD_LEN + 1];
  	bool		issuper = false;	/* Make the user a superuser? */
! 	bool		inherit = true; /* Auto inherit privileges? */
  	bool		createrole = false;		/* Can this user create roles? */
  	bool		createdb = false;		/* Can the user create databases? */
  	bool		canlogin = false;		/* Can this user login? */
  	bool		isreplication = false;	/* Is this a replication role? */
  	bool		bypassrls = false;		/* Is this a row security enabled role? */
  	int			connlimit = -1; /* maximum connections allowed */
  	List	   *addroleto = NIL;	/* roles to make this a member of */
  	List	   *rolemembers = NIL;		/* roles to be members of this role */
--- 74,86 ----
  	bool		encrypt_password = Password_encryption; /* encrypt password? */
  	char		encrypted_password[MD5_PASSWD_LEN + 1];
  	bool		issuper = false;	/* Make the user a superuser? */
! 	bool		inherit = true;	/* Auto inherit privileges? */
  	bool		createrole = false;		/* Can this user create roles? */
  	bool		createdb = false;		/* Can the user create databases? */
  	bool		canlogin = false;		/* Can this user login? */
  	bool		isreplication = false;	/* Is this a replication role? */
  	bool		bypassrls = false;		/* Is this a row security enabled role? */
+ 	RoleAttr	attributes = ROLE_ATTR_NONE;	/* role attributes, initialized to none. */
  	int			connlimit = -1; /* maximum connections allowed */
  	List	   *addroleto = NIL;	/* roles to make this a member of */
  	List	   *rolemembers = NIL;		/* roles to be members of this role */
*************** CreateRole(CreateRoleStmt *stmt)
*** 249,254 ****
--- 242,249 ----
  
  	if (dpassword && dpassword->arg)
  		password = strVal(dpassword->arg);
+ 
+ 	/* Role Attributes */
  	if (dissuper)
  		issuper = intVal(dissuper->arg) != 0;
  	if (dinherit)
*************** CreateRole(CreateRoleStmt *stmt)
*** 261,266 ****
--- 256,264 ----
  		canlogin = intVal(dcanlogin->arg) != 0;
  	if (disreplication)
  		isreplication = intVal(disreplication->arg) != 0;
+ 	if (dbypassRLS)
+ 		bypassrls = intVal(dbypassRLS->arg) != 0;
+ 
  	if (dconnlimit)
  	{
  		connlimit = intVal(dconnlimit->arg);
*************** CreateRole(CreateRoleStmt *stmt)
*** 277,284 ****
  		adminmembers = (List *) dadminmembers->arg;
  	if (dvalidUntil)
  		validUntil = strVal(dvalidUntil->arg);
- 	if (dbypassRLS)
- 		bypassrls = intVal(dbypassRLS->arg) != 0;
  
  	/* Check some permissions first */
  	if (issuper)
--- 275,280 ----
*************** CreateRole(CreateRoleStmt *stmt)
*** 304,310 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to create role")));
--- 300,306 ----
  	}
  	else
  	{
! 		if (!has_role_attribute(GetUserId(), ROLE_ATTR_CREATEROLE))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to create role")));
*************** CreateRole(CreateRoleStmt *stmt)
*** 355,360 ****
--- 351,372 ----
  								validUntil_datum,
  								validUntil_null);
  
+ 	/* Set all role attributes */
+ 	if (issuper)
+ 		attributes |= ROLE_ATTR_SUPERUSER;
+ 	if (inherit)
+ 		attributes |= ROLE_ATTR_INHERIT;
+ 	if (createrole)
+ 		attributes |= ROLE_ATTR_CREATEROLE;
+ 	if (createdb)
+ 		attributes |= ROLE_ATTR_CREATEDB;
+ 	if (canlogin)
+ 		attributes |= ROLE_ATTR_CANLOGIN;
+ 	if (isreplication)
+ 		attributes |= ROLE_ATTR_REPLICATION;
+ 	if (bypassrls)
+ 		attributes |= ROLE_ATTR_BYPASSRLS;
+ 
  	/*
  	 * Build a tuple to insert
  	 */
*************** CreateRole(CreateRoleStmt *stmt)
*** 364,377 ****
  	new_record[Anum_pg_authid_rolname - 1] =
  		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
  
! 	new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
! 	new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
! 	new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
! 	new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
! 	/* superuser gets catupdate right by default */
! 	new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper);
! 	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
! 	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
  	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
  
  	if (password)
--- 376,383 ----
  	new_record[Anum_pg_authid_rolname - 1] =
  		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
  
! 	new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
! 
  	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
  
  	if (password)
*************** CreateRole(CreateRoleStmt *stmt)
*** 394,401 ****
  	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
  	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
  
- 	new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls);
- 
  	tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
  
  	/*
--- 400,405 ----
*************** AlterRole(AlterRoleStmt *stmt)
*** 508,513 ****
--- 512,518 ----
  	DefElem    *dvalidUntil = NULL;
  	DefElem    *dbypassRLS = NULL;
  	Oid			roleid;
+ 	RoleAttr attributes;
  
  	/* Extract options from the statement node tree */
  	foreach(option, stmt->options)
*************** AlterRole(AlterRoleStmt *stmt)
*** 661,688 ****
  	 * To mess with a superuser you gotta be superuser; else you need
  	 * createrole, or just want to change your own password
  	 */
! 	if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper || issuper >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter superusers")));
  	}
! 	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolreplication || isreplication >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter replication users")));
  	}
! 	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolbypassrls || bypassrls >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to change bypassrls attribute")));
  	}
! 	else if (!have_createrole_privilege())
  	{
  		if (!(inherit < 0 &&
  			  createrole < 0 &&
--- 666,696 ----
  	 * To mess with a superuser you gotta be superuser; else you need
  	 * createrole, or just want to change your own password
  	 */
! 
! 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 
! 	if ((attributes & ROLE_ATTR_SUPERUSER) || issuper >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter superusers")));
  	}
! 	else if ((attributes & ROLE_ATTR_REPLICATION) || isreplication >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter replication users")));
  	}
! 	else if ((attributes & ROLE_ATTR_BYPASSRLS) || bypassrls >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to change bypassrls attribute")));
  	}
! 	else if (!has_role_attribute(GetUserId(), ROLE_ATTR_CREATEROLE))
  	{
  		if (!(inherit < 0 &&
  			  createrole < 0 &&
*************** AlterRole(AlterRoleStmt *stmt)
*** 743,785 ****
  	 */
  	if (issuper >= 0)
  	{
! 		new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);
! 		new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
! 
! 		new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);
! 		new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true;
  	}
  
  	if (inherit >= 0)
  	{
! 		new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
! 		new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
  	}
  
  	if (createrole >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);
! 		new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
  	}
  
  	if (createdb >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0);
! 		new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true;
  	}
  
  	if (canlogin >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0);
! 		new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true;
  	}
  
  	if (isreplication >= 0)
  	{
! 		new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication > 0);
! 		new_record_repl[Anum_pg_authid_rolreplication - 1] = true;
  	}
  
  	if (dconnlimit)
  	{
  		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
--- 751,807 ----
  	 */
  	if (issuper >= 0)
  	{
! 		attributes = (issuper > 0) ? attributes | (ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE)
! 								   : attributes & ~(ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (inherit >= 0)
  	{
! 		attributes = (inherit > 0) ? attributes | ROLE_ATTR_INHERIT
! 								   : attributes & ~(ROLE_ATTR_INHERIT);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (createrole >= 0)
  	{
! 		attributes = (createrole > 0) ? attributes | ROLE_ATTR_CREATEROLE
! 									  : attributes & ~(ROLE_ATTR_CREATEROLE);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (createdb >= 0)
  	{
! 		attributes = (createdb > 0) ? attributes | ROLE_ATTR_CREATEDB
! 									: attributes & ~(ROLE_ATTR_CREATEDB);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (canlogin >= 0)
  	{
! 		attributes = (canlogin > 0) ? attributes | ROLE_ATTR_CANLOGIN
! 									: attributes & ~(ROLE_ATTR_CANLOGIN);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (isreplication >= 0)
  	{
! 		attributes = (isreplication > 0) ? attributes | ROLE_ATTR_REPLICATION
! 										 : attributes & ~(ROLE_ATTR_REPLICATION);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
! 	}
! 
! 	if (bypassrls >= 0)
! 	{
! 		attributes = (bypassrls > 0) ? attributes | ROLE_ATTR_BYPASSRLS
! 										 : attributes & ~(ROLE_ATTR_BYPASSRLS);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
+ 	/* If any role attributes were set, then update. */
+ 	if (new_record_repl[Anum_pg_authid_rolattr - 1])
+ 		new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
+ 
  	if (dconnlimit)
  	{
  		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
*************** AlterRole(AlterRoleStmt *stmt)
*** 815,825 ****
  	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
  	new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
  
- 	if (bypassrls >= 0)
- 	{
- 		new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls > 0);
- 		new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
- 	}
  
  	new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
  								  new_record_nulls, new_record_repl);
--- 837,842 ----
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 867,872 ****
--- 884,890 ----
  	HeapTuple	roletuple;
  	Oid			databaseid = InvalidOid;
  	Oid			roleid = InvalidOid;
+ 	RoleAttr	attributes;
  
  	if (stmt->role)
  	{
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 889,895 ****
  		 * To mess with a superuser you gotta be superuser; else you need
  		 * createrole, or just want to change your own settings
  		 */
! 		if (((Form_pg_authid) GETSTRUCT(roletuple))->rolsuper)
  		{
  			if (!superuser())
  				ereport(ERROR,
--- 907,914 ----
  		 * To mess with a superuser you gotta be superuser; else you need
  		 * createrole, or just want to change your own settings
  		 */
! 		attributes = ((Form_pg_authid) GETSTRUCT(roletuple))->rolattr;
! 		if (attributes & ROLE_ATTR_SUPERUSER)
  		{
  			if (!superuser())
  				ereport(ERROR,
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 898,904 ****
  		}
  		else
  		{
! 			if (!have_createrole_privilege() &&
  				HeapTupleGetOid(roletuple) != GetUserId())
  				ereport(ERROR,
  						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 917,923 ----
  		}
  		else
  		{
! 			if (!has_role_attribute(GetUserId(), ROLE_ATTR_CREATEROLE) &&
  				HeapTupleGetOid(roletuple) != GetUserId())
  				ereport(ERROR,
  						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
*************** DropRole(DropRoleStmt *stmt)
*** 951,957 ****
  				pg_auth_members_rel;
  	ListCell   *item;
  
! 	if (!have_createrole_privilege())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to drop role")));
--- 970,976 ----
  				pg_auth_members_rel;
  	ListCell   *item;
  
! 	if (!has_role_attribute(GetUserId(), ROLE_ATTR_CREATEROLE))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to drop role")));
*************** DropRole(DropRoleStmt *stmt)
*** 973,978 ****
--- 992,998 ----
  		char	   *detail_log;
  		SysScanDesc sscan;
  		Oid			roleid;
+ 		RoleAttr	attributes;
  
  		tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
  		if (!HeapTupleIsValid(tuple))
*************** DropRole(DropRoleStmt *stmt)
*** 1013,1020 ****
  		 * roles but not superuser roles.  This is mainly to avoid the
  		 * scenario where you accidentally drop the last superuser.
  		 */
! 		if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper &&
! 			!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to drop superusers")));
--- 1033,1040 ----
  		 * roles but not superuser roles.  This is mainly to avoid the
  		 * scenario where you accidentally drop the last superuser.
  		 */
! 		attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 		if ((attributes & ROLE_ATTR_SUPERUSER) && !superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to drop superusers")));
*************** RenameRole(const char *oldname, const ch
*** 1128,1133 ****
--- 1148,1154 ----
  	bool		repl_repl[Natts_pg_authid];
  	int			i;
  	Oid			roleid;
+ 	RoleAttr	attributes;
  
  	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
  	dsc = RelationGetDescr(rel);
*************** RenameRole(const char *oldname, const ch
*** 1173,1179 ****
  	/*
  	 * createrole is enough privilege unless you want to mess with a superuser
  	 */
! 	if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
  	{
  		if (!superuser())
  			ereport(ERROR,
--- 1194,1201 ----
  	/*
  	 * createrole is enough privilege unless you want to mess with a superuser
  	 */
! 	attributes = ((Form_pg_authid) GETSTRUCT(oldtuple))->rolattr;
! 	if (attributes & ROLE_ATTR_SUPERUSER)
  	{
  		if (!superuser())
  			ereport(ERROR,
*************** RenameRole(const char *oldname, const ch
*** 1182,1188 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to rename role")));
--- 1204,1210 ----
  	}
  	else
  	{
! 		if (!has_role_attribute(GetUserId(), ROLE_ATTR_CREATEROLE))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to rename role")));
*************** AddRoleMems(const char *rolename, Oid ro
*** 1409,1415 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege() &&
  			!is_admin_of_role(grantorId, roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 1431,1437 ----
  	}
  	else
  	{
! 		if (!has_role_attribute(GetUserId(), ROLE_ATTR_CREATEROLE) &&
  			!is_admin_of_role(grantorId, roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
*************** DelRoleMems(const char *rolename, Oid ro
*** 1555,1561 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege() &&
  			!is_admin_of_role(GetUserId(), roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 1577,1583 ----
  	}
  	else
  	{
! 		if (!has_role_attribute(GetUserId(), ROLE_ATTR_CREATEROLE) &&
  			!is_admin_of_role(GetUserId(), roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
new file mode 100644
index 6ce8dae..491dc38
*** a/src/backend/commands/variable.c
--- b/src/backend/commands/variable.c
*************** check_session_authorization(char **newva
*** 776,781 ****
--- 776,782 ----
  	Oid			roleid;
  	bool		is_superuser;
  	role_auth_extra *myextra;
+ 	RoleAttr	attributes;
  
  	/* Do nothing for the boot_val default of NULL */
  	if (*newval == NULL)
*************** check_session_authorization(char **newva
*** 800,806 ****
  	}
  
  	roleid = HeapTupleGetOid(roleTup);
! 	is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
  
  	ReleaseSysCache(roleTup);
  
--- 801,808 ----
  	}
  
  	roleid = HeapTupleGetOid(roleTup);
! 	attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
! 	is_superuser = (attributes & ROLE_ATTR_SUPERUSER);
  
  	ReleaseSysCache(roleTup);
  
*************** check_role(char **newval, void **extra,
*** 844,849 ****
--- 846,852 ----
  	Oid			roleid;
  	bool		is_superuser;
  	role_auth_extra *myextra;
+ 	RoleAttr	attributes;
  
  	if (strcmp(*newval, "none") == 0)
  	{
*************** check_role(char **newval, void **extra,
*** 872,878 ****
  		}
  
  		roleid = HeapTupleGetOid(roleTup);
! 		is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
  
  		ReleaseSysCache(roleTup);
  
--- 875,882 ----
  		}
  
  		roleid = HeapTupleGetOid(roleTup);
! 		attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
! 		is_superuser = (attributes & ROLE_ATTR_SUPERUSER);
  
  		ReleaseSysCache(roleTup);
  
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
new file mode 100644
index 1977f09..8d3d13e
*** a/src/backend/replication/logical/logicalfuncs.c
--- b/src/backend/replication/logical/logicalfuncs.c
***************
*** 23,34 ****
--- 23,36 ----
  
  #include "access/xlog_internal.h"
  
+ #include "catalog/pg_authid.h"
  #include "catalog/pg_type.h"
  
  #include "nodes/makefuncs.h"
  
  #include "mb/pg_wchar.h"
  
+ #include "utils/acl.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/inval.h"
*************** XLogRead(char *buf, TimeLineID tli, XLog
*** 205,211 ****
  static void
  check_permissions(void)
  {
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
--- 207,213 ----
  static void
  check_permissions(void)
  {
! 	if (!superuser() && !check_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
new file mode 100644
index bd4701f..807e4a3
*** a/src/backend/replication/slotfuncs.c
--- b/src/backend/replication/slotfuncs.c
***************
*** 17,32 ****
  #include "miscadmin.h"
  
  #include "access/htup_details.h"
  #include "replication/slot.h"
  #include "replication/logical.h"
  #include "replication/logicalfuncs.h"
  #include "utils/builtins.h"
  #include "utils/pg_lsn.h"
  
  static void
  check_permissions(void)
  {
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
--- 17,34 ----
  #include "miscadmin.h"
  
  #include "access/htup_details.h"
+ #include "catalog/pg_authid.h"
  #include "replication/slot.h"
  #include "replication/logical.h"
  #include "replication/logicalfuncs.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/pg_lsn.h"
  
  static void
  check_permissions(void)
  {
! 	if (!superuser() && !check_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
new file mode 100644
index 6c232dc..58633cc
*** a/src/backend/rewrite/rowsecurity.c
--- b/src/backend/rewrite/rowsecurity.c
***************
*** 36,41 ****
--- 36,42 ----
  #include "access/heapam.h"
  #include "access/htup_details.h"
  #include "access/sysattr.h"
+ #include "catalog/pg_authid.h"
  #include "catalog/pg_class.h"
  #include "catalog/pg_inherits_fn.h"
  #include "catalog/pg_policy.h"
*************** check_enable_rls(Oid relid, Oid checkAsU
*** 521,527 ****
  	 */
  	if (!checkAsUser && row_security == ROW_SECURITY_OFF)
  	{
! 		if (has_bypassrls_privilege(user_id))
  			/* OK to bypass */
  			return RLS_NONE_ENV;
  		else
--- 522,528 ----
  	 */
  	if (!checkAsUser && row_security == ROW_SECURITY_OFF)
  	{
! 		if (has_role_attribute(user_id, ROLE_ATTR_BYPASSRLS))
  			/* OK to bypass */
  			return RLS_NONE_ENV;
  		else
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
new file mode 100644
index dc6eb2c..f23e135
*** a/src/backend/utils/adt/acl.c
--- b/src/backend/utils/adt/acl.c
*************** static Oid	convert_type_name(text *typen
*** 115,120 ****
--- 115,121 ----
  static AclMode convert_type_priv_string(text *priv_type_text);
  static AclMode convert_role_priv_string(text *priv_type_text);
  static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
+ static RoleAttr convert_role_attr_string(text *attr_type_text);
  
  static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue);
  
*************** pg_role_aclcheck(Oid role_oid, Oid rolei
*** 4602,4607 ****
--- 4603,4773 ----
  	return ACLCHECK_NO_PRIV;
  }
  
+ /*
+  * pg_has_role_attribute_id_attr
+  *		Check that the role with the given oid has the given named role
+  *		attribute.
+  *
+  * Note: This function applies superuser checks.  Therefore, if the provided
+  * role is a superuser, then the result will always be true.
+  */
+ Datum
+ pg_has_role_attribute_id_attr(PG_FUNCTION_ARGS)
+ {
+ 	Oid			roleoid = PG_GETARG_OID(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(has_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_has_role_attribute_name_attr
+  *		Check that the named role has the given named role attribute.
+  *
+  * Note: This function applies superuser checks.  Therefore, if the provided
+  * role is a superuser, then the result will always be true.
+  */
+ Datum
+ pg_has_role_attribute_name_attr(PG_FUNCTION_ARGS)
+ {
+ 	Name		rolename = PG_GETARG_NAME(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	Oid			roleoid;
+ 	RoleAttr	attribute;
+ 
+ 	roleoid = get_role_oid(NameStr(*rolename), false);
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(has_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_check_role_attribute_id_attr
+  *		Check that the role with the given oid has the given named role
+  *		attribute.
+  *
+  * Note: This function is different from 'pg_has_role_attribute_id_attr' in that
+  * it does *not* apply any superuser checks.  Therefore, this function will
+  * always return the set value of the attribute, despite the superuser-ness of
+  * the provided role.
+  */
+ Datum
+ pg_check_role_attribute_id_attr(PG_FUNCTION_ARGS)
+ {
+ 	Oid			roleoid = PG_GETARG_OID(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(check_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_check_role_attribute_name_attr
+  *		Check that the named role has the given named role attribute.
+  *
+  * Note: This function is different from 'pg_has_role_attribute_name_attr' in
+  * that it does *not* apply any superuser checks.  Therefore, this function will
+  * always return the set value of the attribute, despite the superuser-ness of
+  * the provided role.
+  */
+ Datum
+ pg_check_role_attribute_name_attr(PG_FUNCTION_ARGS)
+ {
+ 	Name		rolename = PG_GETARG_NAME(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	Oid			roleoid;
+ 	RoleAttr	attribute;
+ 
+ 	roleoid = get_role_oid(NameStr(*rolename), false);
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(check_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_all_role_attributes_attrs
+  *		Convert a RoleAttr representation of role attributes into an array of
+  *		corresponding text values.
+  *
+  * The first and only argument is a RoleAttr (int64) representation of the
+  * role attributes.
+  */
+ Datum
+ pg_all_role_attributes_attrs(PG_FUNCTION_ARGS)
+ {
+ 	RoleAttr		attributes = PG_GETARG_INT64(0);
+ 	Datum		   *temp_array;
+ 	ArrayType	   *result;
+ 	int				i = 0;
+ 
+ 	/*
+ 	 * If no attributes are assigned, then there is no need to go through the
+ 	 * individual checks for which are assigned.  Therefore, we can short circuit
+ 	 * and return an empty array.
+ 	 */
+ 	if (attributes == ROLE_ATTR_NONE)
+ 		PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));
+ 
+ 	temp_array = (Datum *) palloc(N_ROLE_ATTRIBUTES * sizeof(Datum));
+ 
+ 	/* Determine which attributes are assigned. */
+ 	if (attributes & ROLE_ATTR_SUPERUSER)
+ 		temp_array[i++] = CStringGetTextDatum("Superuser");
+ 	if (attributes & ROLE_ATTR_INHERIT)
+ 		temp_array[i++] = CStringGetTextDatum("Inherit");
+ 	if (attributes & ROLE_ATTR_CREATEROLE)
+ 		temp_array[i++] = CStringGetTextDatum("Create Role");
+ 	if (attributes & ROLE_ATTR_CREATEDB)
+ 		temp_array[i++] = CStringGetTextDatum("Create DB");
+ 	if (attributes & ROLE_ATTR_CATUPDATE)
+ 		temp_array[i++] = CStringGetTextDatum("Catalog Update");
+ 	if (attributes & ROLE_ATTR_CANLOGIN)
+ 		temp_array[i++] = CStringGetTextDatum("Login");
+ 	if (attributes & ROLE_ATTR_REPLICATION)
+ 		temp_array[i++] = CStringGetTextDatum("Replication");
+ 	if (attributes & ROLE_ATTR_BYPASSRLS)
+ 		temp_array[i++] = CStringGetTextDatum("Bypass RLS");
+ 
+ 	result = construct_array(temp_array, i, TEXTOID, -1, false, 'i');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * convert_role_attr_string
+  *		Convert text string to RoleAttr value.
+  */
+ static RoleAttr
+ convert_role_attr_string(text *attr_type_text)
+ {
+ 	char	   *attr_type = text_to_cstring(attr_type_text);
+ 
+ 	if (pg_strcasecmp(attr_type, "SUPERUSER") == 0)
+ 		return ROLE_ATTR_SUPERUSER;
+ 	else if (pg_strcasecmp(attr_type, "INHERIT") == 0)
+ 		return ROLE_ATTR_INHERIT;
+ 	else if (pg_strcasecmp(attr_type, "CREATEROLE") == 0)
+ 		return ROLE_ATTR_CREATEROLE;
+ 	else if (pg_strcasecmp(attr_type, "CREATEDB") == 0)
+ 		return ROLE_ATTR_CREATEDB;
+ 	else if (pg_strcasecmp(attr_type, "CATUPDATE") == 0)
+ 		return ROLE_ATTR_CATUPDATE;
+ 	else if (pg_strcasecmp(attr_type, "CANLOGIN") == 0)
+ 		return ROLE_ATTR_CANLOGIN;
+ 	else if (pg_strcasecmp(attr_type, "REPLICATION") == 0)
+ 		return ROLE_ATTR_REPLICATION;
+ 	else if (pg_strcasecmp(attr_type, "BYPASSRLS") == 0)
+ 		return ROLE_ATTR_BYPASSRLS;
+ 	else
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("unrecognized role attribute: \"%s\"", attr_type)));
+ }
  
  /*
   * initialization function (called by InitPostgres)
*************** RoleMembershipCacheCallback(Datum arg, i
*** 4634,4656 ****
  }
  
  
- /* Check if specified role has rolinherit set */
- static bool
- has_rolinherit(Oid roleid)
- {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
- 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (HeapTupleIsValid(utup))
- 	{
- 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
- 		ReleaseSysCache(utup);
- 	}
- 	return result;
- }
- 
- 
  /*
   * Get a list of roles that the specified roleid has the privileges of
   *
--- 4800,4805 ----
*************** roles_has_privs_of(Oid roleid)
*** 4697,4703 ****
  		int			i;
  
  		/* Ignore non-inheriting roles */
! 		if (!has_rolinherit(memberid))
  			continue;
  
  		/* Find roles that memberid is directly a member of */
--- 4846,4852 ----
  		int			i;
  
  		/* Ignore non-inheriting roles */
! 		if (!has_role_attribute(memberid, ROLE_ATTR_INHERIT))
  			continue;
  
  		/* Find roles that memberid is directly a member of */
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
new file mode 100644
index 2f02303..3297ea7
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
***************
*** 33,38 ****
--- 33,39 ----
  #include "access/htup_details.h"
  #include "access/sysattr.h"
  #include "access/xact.h"
+ #include "catalog/pg_authid.h"
  #include "catalog/pg_collation.h"
  #include "catalog/pg_constraint.h"
  #include "catalog/pg_operator.h"
***************
*** 43,48 ****
--- 44,50 ----
  #include "parser/parse_coerce.h"
  #include "parser/parse_relation.h"
  #include "miscadmin.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
*************** RI_Initial_Check(Trigger *trigger, Relat
*** 2308,2314 ****
  	 * bypassrls right or is the table owner of the table(s) involved which
  	 * have RLS enabled.
  	 */
! 	if (!has_bypassrls_privilege(GetUserId()) &&
  		((pk_rel->rd_rel->relrowsecurity &&
  		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
  		 (fk_rel->rd_rel->relrowsecurity &&
--- 2310,2316 ----
  	 * bypassrls right or is the table owner of the table(s) involved which
  	 * have RLS enabled.
  	 */
! 	if (!has_role_attribute(GetUserId(), ROLE_ATTR_BYPASSRLS) &&
  		((pk_rel->rd_rel->relrowsecurity &&
  		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
  		 (fk_rel->rd_rel->relrowsecurity &&
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
new file mode 100644
index 8fccb4c..db2a0fb
*** a/src/backend/utils/init/miscinit.c
--- b/src/backend/utils/init/miscinit.c
***************
*** 40,45 ****
--- 40,46 ----
  #include "storage/pg_shmem.h"
  #include "storage/proc.h"
  #include "storage/procarray.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/guc.h"
  #include "utils/memutils.h"
*************** SetUserIdAndContext(Oid userid, bool sec
*** 329,352 ****
  
  
  /*
-  * Check whether specified role has explicit REPLICATION privilege
-  */
- bool
- has_rolreplication(Oid roleid)
- {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
- 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (HeapTupleIsValid(utup))
- 	{
- 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication;
- 		ReleaseSysCache(utup);
- 	}
- 	return result;
- }
- 
- /*
   * Initialize user identity during normal backend startup
   */
  void
--- 330,335 ----
*************** InitializeSessionUserId(const char *role
*** 375,381 ****
  	roleid = HeapTupleGetOid(roleTup);
  
  	AuthenticatedUserId = roleid;
! 	AuthenticatedUserIsSuperuser = rform->rolsuper;
  
  	/* This sets OuterUserId/CurrentUserId too */
  	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
--- 358,364 ----
  	roleid = HeapTupleGetOid(roleTup);
  
  	AuthenticatedUserId = roleid;
! 	AuthenticatedUserIsSuperuser = (rform->rolattr & ROLE_ATTR_SUPERUSER);
  
  	/* This sets OuterUserId/CurrentUserId too */
  	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
*************** InitializeSessionUserId(const char *role
*** 394,400 ****
  		/*
  		 * Is role allowed to login at all?
  		 */
! 		if (!rform->rolcanlogin)
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  					 errmsg("role \"%s\" is not permitted to log in",
--- 377,383 ----
  		/*
  		 * Is role allowed to login at all?
  		 */
! 		if (!(rform->rolattr & ROLE_ATTR_CANLOGIN))
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  					 errmsg("role \"%s\" is not permitted to log in",
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
new file mode 100644
index c348034..be0e1cc
*** a/src/backend/utils/init/postinit.c
--- b/src/backend/utils/init/postinit.c
*************** InitPostgres(const char *in_dbname, Oid
*** 762,768 ****
  	{
  		Assert(!bootstrap);
  
! 		if (!superuser() && !has_rolreplication(GetUserId()))
  			ereport(FATAL,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser or replication role to start walsender")));
--- 762,768 ----
  	{
  		Assert(!bootstrap);
  
! 		if (!superuser() && !check_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION))
  			ereport(FATAL,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser or replication role to start walsender")));
diff --git a/src/backend/utils/misc/superuser.c b/src/backend/utils/misc/superuser.c
new file mode 100644
index ff0f947..9af77ed
*** a/src/backend/utils/misc/superuser.c
--- b/src/backend/utils/misc/superuser.c
***************
*** 22,27 ****
--- 22,28 ----
  
  #include "access/htup_details.h"
  #include "catalog/pg_authid.h"
+ #include "utils/acl.h"
  #include "utils/inval.h"
  #include "utils/syscache.h"
  #include "miscadmin.h"
*************** superuser_arg(Oid roleid)
*** 58,63 ****
--- 59,65 ----
  {
  	bool		result;
  	HeapTuple	rtup;
+ 	RoleAttr	attributes;
  
  	/* Quick out for cache hit */
  	if (OidIsValid(last_roleid) && last_roleid == roleid)
*************** superuser_arg(Oid roleid)
*** 71,77 ****
  	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(rtup))
  	{
! 		result = ((Form_pg_authid) GETSTRUCT(rtup))->rolsuper;
  		ReleaseSysCache(rtup);
  	}
  	else
--- 73,80 ----
  	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(rtup))
  	{
! 		attributes = ((Form_pg_authid) GETSTRUCT(rtup))->rolattr;
! 		result = (attributes & ROLE_ATTR_SUPERUSER);
  		ReleaseSysCache(rtup);
  	}
  	else
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
new file mode 100644
index eb633bc..f638167
*** a/src/bin/pg_dump/pg_dumpall.c
--- b/src/bin/pg_dump/pg_dumpall.c
*************** dumpRoles(PGconn *conn)
*** 671,680 ****
  	/* note: rolconfig is dumped later */
  	if (server_version >= 90500)
  		printfPQExpBuffer(buf,
! 						  "SELECT oid, rolname, rolsuper, rolinherit, "
! 						  "rolcreaterole, rolcreatedb, "
! 						  "rolcanlogin, rolconnlimit, rolpassword, "
! 						  "rolvaliduntil, rolreplication, rolbypassrls, "
  			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
  						  "rolname = current_user AS is_current_user "
  						  "FROM pg_authid "
--- 671,686 ----
  	/* note: rolconfig is dumped later */
  	if (server_version >= 90500)
  		printfPQExpBuffer(buf,
! 						  "SELECT oid, rolname, "
! 						  "pg_check_role_attribute(oid, 'SUPERUSER') AS rolsuper, "
! 						  "pg_check_role_attribute(oid, 'INHERIT') AS rolinherit, "
! 						  "pg_check_role_attribute(oid, 'CREATEROLE') AS rolcreaterole, "
! 						  "pg_check_role_attribute(oid, 'CREATEDB') AS rolcreatedb, "
! 						  "pg_check_role_attribute(oid, 'CANLOGIN') AS rolcanlogin, "
! 						  "pg_check_role_attribute(oid, 'REPLICATION') AS rolreplication, "
! 						  "pg_check_role_attribute(oid, 'BYPASSRLS') AS rolbypassrls, "
! 						  "rolconnlimit, rolpassword, "
! 						  "rolvaliduntil, "
  			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
  						  "rolname = current_user AS is_current_user "
  						  "FROM pg_authid "
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
new file mode 100644
index 3b63d2b..1fe0407
*** a/src/include/catalog/pg_authid.h
--- b/src/include/catalog/pg_authid.h
***************
*** 45,60 ****
  CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
  {
  	NameData	rolname;		/* name of role */
! 	bool		rolsuper;		/* read this field via superuser() only! */
! 	bool		rolinherit;		/* inherit privileges from other roles? */
! 	bool		rolcreaterole;	/* allowed to create more roles? */
! 	bool		rolcreatedb;	/* allowed to create databases? */
! 	bool		rolcatupdate;	/* allowed to alter catalogs manually? */
! 	bool		rolcanlogin;	/* allowed to log in as session user? */
! 	bool		rolreplication; /* role used for streaming replication */
! 	bool		rolbypassrls;	/* allowed to bypass row level security? */
  	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
- 
  	/* remaining fields may be null; use heap_getattr to read them! */
  	text		rolpassword;	/* password, if any */
  	timestamptz rolvaliduntil;	/* password expiration time, if any */
--- 45,52 ----
  CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
  {
  	NameData	rolname;		/* name of role */
! 	int64		rolattr;		/* role attribute bitmask */
  	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
  	/* remaining fields may be null; use heap_getattr to read them! */
  	text		rolpassword;	/* password, if any */
  	timestamptz rolvaliduntil;	/* password expiration time, if any */
*************** typedef FormData_pg_authid *Form_pg_auth
*** 74,101 ****
   *		compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid					12
  #define Anum_pg_authid_rolname			1
! #define Anum_pg_authid_rolsuper			2
! #define Anum_pg_authid_rolinherit		3
! #define Anum_pg_authid_rolcreaterole	4
! #define Anum_pg_authid_rolcreatedb		5
! #define Anum_pg_authid_rolcatupdate		6
! #define Anum_pg_authid_rolcanlogin		7
! #define Anum_pg_authid_rolreplication	8
! #define Anum_pg_authid_rolbypassrls		9
! #define Anum_pg_authid_rolconnlimit		10
! #define Anum_pg_authid_rolpassword		11
! #define Anum_pg_authid_rolvaliduntil	12
  
  /* ----------------
   *		initial contents of pg_authid
   *
   * The uppercase quantities will be replaced at initdb time with
   * user choices.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t -1 _null_ _null_));
  
  #define BOOTSTRAP_SUPERUSERID 10
  
--- 66,118 ----
   *		compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid					5
  #define Anum_pg_authid_rolname			1
! #define Anum_pg_authid_rolattr			2
! #define Anum_pg_authid_rolconnlimit		3
! #define Anum_pg_authid_rolpassword		4
! #define Anum_pg_authid_rolvaliduntil	5
! 
! /* ----------------
!  * Role attributes are encoded so that we can OR them together in a bitmask.
!  * The present representation of RoleAttr (defined in acl.h) limits us to 64
!  * distinct rights.
!  * ----------------
!  */
! #define ROLE_ATTR_SUPERUSER		(1<<0)
! #define ROLE_ATTR_INHERIT		(1<<1)
! #define ROLE_ATTR_CREATEROLE	(1<<2)
! #define ROLE_ATTR_CREATEDB		(1<<3)
! #define ROLE_ATTR_CATUPDATE		(1<<4)
! #define ROLE_ATTR_CANLOGIN		(1<<5)
! #define ROLE_ATTR_REPLICATION	(1<<6)
! #define ROLE_ATTR_BYPASSRLS		(1<<7)
! #define N_ROLE_ATTRIBUTES		8		/* 1 plus the last 1<<x */
! #define ROLE_ATTR_NONE			0
! 
! /* ----------------
!  * All currently available attributes.
!  *
!  * Note: This value is currently used by genbki.pl.  Unfortunately, we have to
!  * hard code this value as we cannot use an approach like (1 << N_ROLE_ATTRIBUTES) - 1
!  * as genbki.pl simply uses the literal value associated with the #define symbol
!  * which causes an incorrect substitution. Therefore, whenever new role attributes
!  * are added this value MUST be changed as well.
!  * ----------------
!  */
! #define ROLE_ATTR_ALL          255 /* or (1 << N_ROLE_ATTRIBUTES) - 1 */
  
  /* ----------------
   *		initial contents of pg_authid
   *
   * The uppercase quantities will be replaced at initdb time with
   * user choices.
+  *
+  * PGROLATTRALL is substituted by genbki.pl to use the value defined by
+  * ROLE_ATTR_ALL.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" PGROLATTRALL -1 _null_ _null_));
  
  #define BOOTSTRAP_SUPERUSERID 10
  
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index deba4b1..261cbca
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("rank of hypothetical row without
*** 5100,5105 ****
--- 5100,5116 ----
  DATA(insert OID = 3993 ( dense_rank_final	PGNSP PGUID 12 1 0 2276 0 f f f f f f i 2 0 20 "2281 2276" "{2281,2276}" "{i,v}" _null_ _null_	hypothetical_dense_rank_final _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  
+ /* role attribute support functions */
+ DATA(insert OID = 3994 ( pg_has_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ pg_has_role_attribute_id_attr _null_ _null_ _null_ ));
+ DESCR("check role attribute by role oid with superuser bypass check");
+ DATA(insert OID = 3995 ( pg_has_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "19 25" _null_ _null_ _null_ _null_ pg_has_role_attribute_name_attr _null_ _null_ _null_ ));
+ DESCR("check role attribute by role name with superuser bypass check");
+ DATA(insert OID = 3996 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_id_attr _null_ _null_ _null_ ));
+ DESCR("check role attribute by role id");
+ DATA(insert OID = 3997 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "19 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_name_attr _null_ _null_ _null_ ));
+ DESCR("check role attribute by role name");
+ DATA(insert OID = 3998 ( pg_all_role_attributes		PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 1009 "20" _null_ _null_ _null_ _null_ pg_all_role_attributes_attrs _null_ _null_ _null_));
+ DESCR("convert role attributes to string array");
  
  /*
   * Symbolic values for provolatile column: these indicate whether the result
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
new file mode 100644
index a8e3164..0755913
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
*************** typedef enum AclObjectKind
*** 200,205 ****
--- 200,206 ----
  	MAX_ACL_KIND				/* MUST BE LAST */
  } AclObjectKind;
  
+ typedef uint64 RoleAttr;		/* a bitmask for role attribute bits */
  
  /*
   * routines used internally
*************** extern bool pg_foreign_data_wrapper_owne
*** 326,332 ****
  extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
  extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
  extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
! extern bool has_createrole_privilege(Oid roleid);
! extern bool has_bypassrls_privilege(Oid roleid);
  
  #endif   /* ACL_H */
--- 327,335 ----
  extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
  extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
  extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
! 
! /* role attribute check routines */
! extern bool has_role_attribute(Oid roleid, RoleAttr attribute);
! extern bool check_role_attribute(Oid roleid, RoleAttr attribute);
  
  #endif   /* ACL_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index 565cff3..921cd0c
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum pg_has_role_id_name(PG_FUNC
*** 106,111 ****
--- 106,116 ----
  extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS);
  extern Datum pg_has_role_name(PG_FUNCTION_ARGS);
  extern Datum pg_has_role_id(PG_FUNCTION_ARGS);
+ extern Datum pg_has_role_attribute_id_attr(PG_FUNCTION_ARGS);
+ extern Datum pg_has_role_attribute_name_attr(PG_FUNCTION_ARGS);
+ extern Datum pg_check_role_attribute_id_attr(PG_FUNCTION_ARGS);
+ extern Datum pg_check_role_attribute_name_attr(PG_FUNCTION_ARGS);
+ extern Datum pg_all_role_attributes_attrs(PG_FUNCTION_ARGS);
  
  /* bool.c */
  extern Datum boolin(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
new file mode 100644
index 80c3351..cec0b61
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
*************** pg_group| SELECT pg_authid.rolname AS gr
*** 1314,1320 ****
             FROM pg_auth_members
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
!   WHERE (NOT pg_authid.rolcanlogin);
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,
--- 1314,1320 ----
             FROM pg_auth_members
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
!   WHERE (NOT pg_check_role_attribute(pg_authid.oid, 'CANLOGIN'::text));
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,
*************** pg_replication_slots| SELECT l.slot_name
*** 1405,1421 ****
     FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, xmin, catalog_xmin, restart_lsn)
       LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
  pg_roles| SELECT pg_authid.rolname,
!     pg_authid.rolsuper,
!     pg_authid.rolinherit,
!     pg_authid.rolcreaterole,
!     pg_authid.rolcreatedb,
!     pg_authid.rolcatupdate,
!     pg_authid.rolcanlogin,
!     pg_authid.rolreplication,
      pg_authid.rolconnlimit,
      '********'::text AS rolpassword,
      pg_authid.rolvaliduntil,
-     pg_authid.rolbypassrls,
      s.setconfig AS rolconfig,
      pg_authid.oid
     FROM (pg_authid
--- 1405,1421 ----
     FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, xmin, catalog_xmin, restart_lsn)
       LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
  pg_roles| SELECT pg_authid.rolname,
!     pg_check_role_attribute(pg_authid.oid, 'SUPERUSER'::text) AS rolsuper,
!     pg_check_role_attribute(pg_authid.oid, 'INHERIT'::text) AS rolinherit,
!     pg_check_role_attribute(pg_authid.oid, 'CREATEROLE'::text) AS rolcreaterole,
!     pg_check_role_attribute(pg_authid.oid, 'CREATEDB'::text) AS rolcreatedb,
!     pg_check_role_attribute(pg_authid.oid, 'CATUPDATE'::text) AS rolcatupdate,
!     pg_check_role_attribute(pg_authid.oid, 'CANLOGIN'::text) AS rolcanlogin,
!     pg_check_role_attribute(pg_authid.oid, 'REPLICATION'::text) AS rolreplication,
!     pg_check_role_attribute(pg_authid.oid, 'BYPASSRLS'::text) AS rolbypassrls,
      pg_authid.rolconnlimit,
      '********'::text AS rolpassword,
      pg_authid.rolvaliduntil,
      s.setconfig AS rolconfig,
      pg_authid.oid
     FROM (pg_authid
*************** pg_settings| SELECT a.name,
*** 1608,1623 ****
     FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline);
  pg_shadow| SELECT pg_authid.rolname AS usename,
      pg_authid.oid AS usesysid,
!     pg_authid.rolcreatedb AS usecreatedb,
!     pg_authid.rolsuper AS usesuper,
!     pg_authid.rolcatupdate AS usecatupd,
!     pg_authid.rolreplication AS userepl,
      pg_authid.rolpassword AS passwd,
      (pg_authid.rolvaliduntil)::abstime AS valuntil,
      s.setconfig AS useconfig
     FROM (pg_authid
       LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
!   WHERE pg_authid.rolcanlogin;
  pg_stat_activity| SELECT s.datid,
      d.datname,
      s.pid,
--- 1608,1623 ----
     FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline);
  pg_shadow| SELECT pg_authid.rolname AS usename,
      pg_authid.oid AS usesysid,
!     pg_check_role_attribute(pg_authid.oid, 'CREATEDB'::text) AS usecreatedb,
!     pg_check_role_attribute(pg_authid.oid, 'SUPERUSER'::text) AS usesuper,
!     pg_check_role_attribute(pg_authid.oid, 'CATUPDATE'::text) AS usecatupd,
!     pg_check_role_attribute(pg_authid.oid, 'REPLICATION'::text) AS userepl,
      pg_authid.rolpassword AS passwd,
      (pg_authid.rolvaliduntil)::abstime AS valuntil,
      s.setconfig AS useconfig
     FROM (pg_authid
       LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
!   WHERE pg_check_role_attribute(pg_authid.oid, 'CANLOGIN'::text);
  pg_stat_activity| SELECT s.datid,
      d.datname,
      s.pid,
#17Stephen Frost
sfrost@snowman.net
In reply to: Adam Brightwell (#16)
Re: Role Attribute Bitmask Catalog Representation

Adam,

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

I have attached an updated patch that incorporates the feedback and
recommendations provided.

Thanks. Comments below.

diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
--- 56,62 ----

backupidstr = text_to_cstring(backupid);

! if (!superuser() && !check_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser or replication role to run a backup")));

The point of has_role_attribute() was to avoid the need to explicitly
say "!superuser()" everywhere. The idea with check_role_attribute() is
that we want to present the user (in places like pg_roles) with the
values that are *actually* set.

In other words, the above should just be:

if (!has_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION))

--- 84,90 ----
{
XLogRecPtr	stoppoint;

! if (!superuser() && !check_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
(errmsg("must be superuser or replication role to run a backup"))));

Ditto here.

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
--- 5031,5081 ----
}

/*
! * has_role_attribute
! * Check if the role has the specified role has a specific role attribute.
! * This function will always return true for roles with superuser privileges
! * unless the attribute being checked is CATUPDATE.
*
! * roleid - the oid of the role to check.
! * attribute - the attribute to check.
*/
bool
! has_role_attribute(Oid roleid, RoleAttr attribute)
{
! /*
! * Superusers bypass all permission checking except in the case of CATUPDATE.
! */
! if (!(attribute & ROLE_ATTR_CATUPDATE) && superuser_arg(roleid))
return true;

! return check_role_attribute(roleid, attribute);
}

+ /*
+  * check_role_attribute
+  *   Check if the role with the specified id has been assigned a specific
+  *   role attribute.  This function does not allow any superuser bypass.

I don't think we need to say that it doesn't "allow" superuser bypass.
Instead, I'd comment that has_role_attribute() should be used for
permissions checks while check_role_attribute is for checking what the
value in the rolattr bitmap is and isn't for doing permissions checks
directly.

diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
*************** CreateRole(CreateRoleStmt *stmt)
*** 82,93 ****
bool		encrypt_password = Password_encryption; /* encrypt password? */
char		encrypted_password[MD5_PASSWD_LEN + 1];
bool		issuper = false;	/* Make the user a superuser? */
! 	bool		inherit = true; /* Auto inherit privileges? */
bool		createrole = false;		/* Can this user create roles? */
bool		createdb = false;		/* Can the user create databases? */
bool		canlogin = false;		/* Can this user login? */
bool		isreplication = false;	/* Is this a replication role? */
bool		bypassrls = false;		/* Is this a row security enabled role? */
int			connlimit = -1; /* maximum connections allowed */
List	   *addroleto = NIL;	/* roles to make this a member of */
List	   *rolemembers = NIL;		/* roles to be members of this role */
--- 74,86 ----
bool		encrypt_password = Password_encryption; /* encrypt password? */
char		encrypted_password[MD5_PASSWD_LEN + 1];
bool		issuper = false;	/* Make the user a superuser? */
! 	bool		inherit = true;	/* Auto inherit privileges? */
bool		createrole = false;		/* Can this user create roles? */
bool		createdb = false;		/* Can the user create databases? */
bool		canlogin = false;		/* Can this user login? */
bool		isreplication = false;	/* Is this a replication role? */
bool		bypassrls = false;		/* Is this a row security enabled role? */
+ 	RoleAttr	attributes = ROLE_ATTR_NONE;	/* role attributes, initialized to none. */
int			connlimit = -1; /* maximum connections allowed */
List	   *addroleto = NIL;	/* roles to make this a member of */
List	   *rolemembers = NIL;		/* roles to be members of this role */

Please clean up the whitespace changes- there's no need for the
'inherit' line to change..

diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
*************** XLogRead(char *buf, TimeLineID tli, XLog
*** 205,211 ****
static void
check_permissions(void)
{
! 	if (!superuser() && !has_rolreplication(GetUserId()))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
(errmsg("must be superuser or replication role to use replication slots"))));
--- 207,213 ----
static void
check_permissions(void)
{
! 	if (!superuser() && !check_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
(errmsg("must be superuser or replication role to use replication slots"))));

Another case which should be using has_role_attribute()

diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
--- 17,34 ----
#include "miscadmin.h"

#include "access/htup_details.h"
+ #include "catalog/pg_authid.h"
#include "replication/slot.h"
#include "replication/logical.h"
#include "replication/logicalfuncs.h"
+ #include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/pg_lsn.h"

static void
check_permissions(void)
{
! if (!superuser() && !check_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
(errmsg("must be superuser or replication role to use replication slots"))));

Also here.

diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
*************** pg_role_aclcheck(Oid role_oid, Oid rolei
*** 4602,4607 ****
--- 4603,4773 ----
return ACLCHECK_NO_PRIV;
}

+ /*
+ * pg_has_role_attribute_id_attr

I'm trying to figure out what the point of the trailing "_attr" in the
function name is..? Doesn't seem necessary to have that for these
functions and it'd look a bit cleaner without it, imv.

diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
new file mode 100644
index c348034..be0e1cc
*** a/src/backend/utils/init/postinit.c
--- b/src/backend/utils/init/postinit.c
*************** InitPostgres(const char *in_dbname, Oid
*** 762,768 ****
{
Assert(!bootstrap);
! 		if (!superuser() && !has_rolreplication(GetUserId()))
ereport(FATAL,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser or replication role to start walsender")));
--- 762,768 ----
{
Assert(!bootstrap);

! if (!superuser() && !check_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION))
ereport(FATAL,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser or replication role to start walsender")));

Also here.

! #define ROLE_ATTR_ALL 255 /* or (1 << N_ROLE_ATTRIBUTES) - 1 */

I'd say "equals" or something rather than "or" since that kind of
implies that it's an laternative, but we can't do that as discussed in
the comment (which I like).

! /* role attribute check routines */
! extern bool has_role_attribute(Oid roleid, RoleAttr attribute);
! extern bool check_role_attribute(Oid roleid, RoleAttr attribute);

With all the 'has_role_attribute(GetUserId(), ROLEATTR_BLAH)' cases, I'd
suggest doing the same as 'superuser()' and also provide a simpler
version: 'have_role_attribute(ROLEATTR_BLAH)' which handles doing the
GetUserId() itself. That'd simplify quite a few of the above calls.

I'm pretty happy with the rest of it.

Thanks!

Stephen

#18Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Stephen Frost (#17)
1 attachment(s)
Re: Role Attribute Bitmask Catalog Representation

Stephen,

Thanks for the feedback.

diff --git a/src/backend/access/transam/xlogfuncs.c

b/src/backend/access/transam/xlogfuncs.c

--- 56,62 ----

backupidstr = text_to_cstring(backupid);

! if (!superuser() && !check_role_attribute(GetUserId(),

ROLE_ATTR_REPLICATION))

ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser or replication role to run a

backup")));

The point of has_role_attribute() was to avoid the need to explicitly
say "!superuser()" everywhere. The idea with check_role_attribute() is
that we want to present the user (in places like pg_roles) with the
values that are *actually* set.

In other words, the above should just be:

if (!has_role_attribute(GetUserId(), ROLE_ATTR_REPLICATION))

Yes, I understand. My original thought here was that I was replacing
'has_rolreplication' which didn't perform any superuser check and that I
was trying to adhere to minimal changes. But I agree this would be the
appropriate solution. Fixed.

+ /*
+  * check_role_attribute
+  *   Check if the role with the specified id has been assigned a

specific

+ * role attribute. This function does not allow any superuser

bypass.

I don't think we need to say that it doesn't "allow" superuser bypass.
Instead, I'd comment that has_role_attribute() should be used for
permissions checks while check_role_attribute is for checking what the
value in the rolattr bitmap is and isn't for doing permissions checks
directly.

Ok. Understood. Fixed.

diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
*************** pg_role_aclcheck(Oid role_oid, Oid rolei
*** 4602,4607 ****
--- 4603,4773 ----
return ACLCHECK_NO_PRIV;
}

+ /*
+ * pg_has_role_attribute_id_attr

I'm trying to figure out what the point of the trailing "_attr" in the
function name is..? Doesn't seem necessary to have that for these
functions and it'd look a bit cleaner without it, imv.

So, I was trying to follow what I perceived as the following convention for
these functions: pg_<function_name>_<arg1>_<arg2>_<argN>. So, what "_attr"
represents is the second argument of the function which is the attribute to
check. I could agree that might be unnecessary, but that was my thought
process on it. At any rate, I've removed it.

! #define ROLE_ATTR_ALL 255 /* or (1 << N_ROLE_ATTRIBUTES) - 1 */

I'd say "equals" or something rather than "or" since that kind of
implies that it's an laternative, but we can't do that as discussed in
the comment (which I like).

Agreed. Fixed.

! /* role attribute check routines */
! extern bool has_role_attribute(Oid roleid, RoleAttr attribute);
! extern bool check_role_attribute(Oid roleid, RoleAttr attribute);

With all the 'has_role_attribute(GetUserId(), ROLEATTR_BLAH)' cases, I'd
suggest doing the same as 'superuser()' and also provide a simpler
version: 'have_role_attribute(ROLEATTR_BLAH)' which handles doing the
GetUserId() itself. That'd simplify quite a few of the above calls.

Good point. Added.

Attached is an updated patch.

-Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

Attachments:

role-attribute-bitmask-v4.patchtext/x-patch; charset=US-ASCII; name=role-attribute-bitmask-v4.patchDownload
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
new file mode 100644
index 133143d..8475985
*** a/src/backend/access/transam/xlogfuncs.c
--- b/src/backend/access/transam/xlogfuncs.c
***************
*** 22,32 ****
--- 22,34 ----
  #include "access/xlog_internal.h"
  #include "access/xlogutils.h"
  #include "catalog/catalog.h"
+ #include "catalog/pg_authid.h"
  #include "catalog/pg_type.h"
  #include "funcapi.h"
  #include "miscadmin.h"
  #include "replication/walreceiver.h"
  #include "storage/smgr.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/numeric.h"
  #include "utils/guc.h"
*************** pg_start_backup(PG_FUNCTION_ARGS)
*** 54,60 ****
  
  	backupidstr = text_to_cstring(backupid);
  
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		   errmsg("must be superuser or replication role to run a backup")));
--- 56,62 ----
  
  	backupidstr = text_to_cstring(backupid);
  
! 	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		   errmsg("must be superuser or replication role to run a backup")));
*************** pg_stop_backup(PG_FUNCTION_ARGS)
*** 82,88 ****
  {
  	XLogRecPtr	stoppoint;
  
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		 (errmsg("must be superuser or replication role to run a backup"))));
--- 84,90 ----
  {
  	XLogRecPtr	stoppoint;
  
! 	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		 (errmsg("must be superuser or replication role to run a backup"))));
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
new file mode 100644
index d30612c..db08f93
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
*************** aclcheck_error_type(AclResult aclerr, Oi
*** 3423,3448 ****
  }
  
  
- /* Check if given user has rolcatupdate privilege according to pg_authid */
- static bool
- has_rolcatupdate(Oid roleid)
- {
- 	bool		rolcatupdate;
- 	HeapTuple	tuple;
- 
- 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (!HeapTupleIsValid(tuple))
- 		ereport(ERROR,
- 				(errcode(ERRCODE_UNDEFINED_OBJECT),
- 				 errmsg("role with OID %u does not exist", roleid)));
- 
- 	rolcatupdate = ((Form_pg_authid) GETSTRUCT(tuple))->rolcatupdate;
- 
- 	ReleaseSysCache(tuple);
- 
- 	return rolcatupdate;
- }
- 
  /*
   * Relay for the various pg_*_mask routines depending on object kind
   */
--- 3423,3428 ----
*************** pg_class_aclmask(Oid table_oid, Oid role
*** 3630,3636 ****
  	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
  		IsSystemClass(table_oid, classForm) &&
  		classForm->relkind != RELKIND_VIEW &&
! 		!has_rolcatupdate(roleid) &&
  		!allowSystemTableMods)
  	{
  #ifdef ACLDEBUG
--- 3610,3616 ----
  	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
  		IsSystemClass(table_oid, classForm) &&
  		classForm->relkind != RELKIND_VIEW &&
! 		!has_role_attribute(roleid, ROLE_ATTR_CATUPDATE) &&
  		!allowSystemTableMods)
  	{
  #ifdef ACLDEBUG
*************** pg_extension_ownercheck(Oid ext_oid, Oid
*** 5051,5102 ****
  }
  
  /*
!  * Check whether specified role has CREATEROLE privilege (or is a superuser)
   *
!  * Note: roles do not have owners per se; instead we use this test in
!  * places where an ownership-like permissions test is needed for a role.
!  * Be sure to apply it to the role trying to do the operation, not the
!  * role being operated on!	Also note that this generally should not be
!  * considered enough privilege if the target role is a superuser.
!  * (We don't handle that consideration here because we want to give a
!  * separate error message for such cases, so the caller has to deal with it.)
   */
  bool
! has_createrole_privilege(Oid roleid)
  {
! 	bool		result = false;
! 	HeapTuple	utup;
! 
! 	/* Superusers bypass all permission checking. */
! 	if (superuser_arg(roleid))
  		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  bool
! has_bypassrls_privilege(Oid roleid)
  {
! 	bool		result = false;
! 	HeapTuple	utup;
  
! 	/* Superusers bypass all permission checking. */
! 	if (superuser_arg(roleid))
! 		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolbypassrls;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  /*
--- 5031,5102 ----
  }
  
  /*
!  * has_role_attribute
!  *   Check if the role with the specified id has been assigned a specific role
!  *   attribute.
   *
!  * roleid - the oid of the role to check.
!  * attribute - the attribute to check.
!  *
!  * Note: Use this function for role attribute permission checking as it accounts
!  * for superuser status.  It will always return true for roles with superuser
!  * privileges unless the attribute being checked is CATUPDATE (superusers are not
!  * allowed to bypass CATUPDATE permissions).
   */
  bool
! has_role_attribute(Oid roleid, RoleAttr attribute)
  {
! 	/*
! 	 * Superusers bypass all permission checking except in the case of CATUPDATE.
! 	 */
! 	if (!(attribute & ROLE_ATTR_CATUPDATE) && superuser_arg(roleid))
  		return true;
  
! 	return check_role_attribute(roleid, attribute);
  }
  
+ /*
+  * have_role_attribute
+  *   Convenience function for checking if the role id returned by GetUserId() has
+  *   been assigned a specific role attribute.
+  *
+  * attribute - the attribute to check.
+  */
  bool
! have_role_attribute(RoleAttr attribute)
  {
! 	return has_role_attribute(GetUserId(), attribute);
! }
  
! /*
!  * check_role_attribute
!  *   Check if the role with the specified id has been assigned a specific role
!  *   attribute.
!  *
!  * roleid - the oid of the role to check.
!  * attribute - the attribute to check.
!  *
!  * Note: This function should only be used for checking the value of an individual
!  * attribute in the rolattr bitmap and should *not* be used for permission checking.
!  * For the purposes of permission checking use 'has_role_attribute' instead.
!  */
! bool
! check_role_attribute(Oid roleid, RoleAttr attribute)
! {
! 	RoleAttr	attributes;
! 	HeapTuple	tuple;
  
! 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 
! 	if (!HeapTupleIsValid(tuple))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("role with OID %u does not exist", roleid)));
! 
! 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 	ReleaseSysCache(tuple);
! 
! 	return (attributes & attribute);
  }
  
  /*
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
new file mode 100644
index ca89879..2929b66
*** a/src/backend/catalog/genbki.pl
--- b/src/backend/catalog/genbki.pl
*************** my $BOOTSTRAP_SUPERUSERID =
*** 90,95 ****
--- 90,97 ----
    find_defined_symbol('pg_authid.h', 'BOOTSTRAP_SUPERUSERID');
  my $PG_CATALOG_NAMESPACE =
    find_defined_symbol('pg_namespace.h', 'PG_CATALOG_NAMESPACE');
+ my $ROLE_ATTR_ALL =
+   find_defined_symbol('pg_authid.h', 'ROLE_ATTR_ALL');
  
  # Read all the input header files into internal data structures
  my $catalogs = Catalog::Catalogs(@input_files);
*************** foreach my $catname (@{ $catalogs->{name
*** 144,149 ****
--- 146,152 ----
  			# substitute constant values we acquired above
  			$row->{bki_values} =~ s/\bPGUID\b/$BOOTSTRAP_SUPERUSERID/g;
  			$row->{bki_values} =~ s/\bPGNSP\b/$PG_CATALOG_NAMESPACE/g;
+ 			$row->{bki_values} =~ s/\bPGROLATTRALL/$ROLE_ATTR_ALL/g;
  
  			# Save pg_type info for pg_attribute processing below
  			if ($catname eq 'pg_type')
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
new file mode 100644
index a036c62..79a16b1
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
*************** CREATE VIEW user_mapping_options AS
*** 2884,2890 ****
             CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
             CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                         OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
!                        OR (SELECT rolsuper FROM pg_authid WHERE rolname = current_user) THEN (pg_options_to_table(um.umoptions)).option_value
                       ELSE NULL END AS character_data) AS option_value
      FROM _pg_user_mappings um;
  
--- 2884,2895 ----
             CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
             CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                         OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
!                        OR (
!                             SELECT pg_check_role_attribute(pg_authid.oid, 'SUPERUSER') AS rolsuper
!                             FROM pg_authid
!                             WHERE rolname = current_user
!                           )
!                        THEN (pg_options_to_table(um.umoptions)).option_value
                       ELSE NULL END AS character_data) AS option_value
      FROM _pg_user_mappings um;
  
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
new file mode 100644
index e261307..6df7e4d
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
*************** check_object_ownership(Oid roleid, Objec
*** 1310,1316 ****
  			}
  			else
  			{
! 				if (!has_createrole_privilege(roleid))
  					ereport(ERROR,
  							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  							 errmsg("must have CREATEROLE privilege")));
--- 1310,1316 ----
  			}
  			else
  			{
! 				if (!has_role_attribute(roleid, ROLE_ATTR_CREATEROLE))
  					ereport(ERROR,
  							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  							 errmsg("must have CREATEROLE privilege")));
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
new file mode 100644
index 22b8cee..cb0e193
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 9,25 ****
  CREATE VIEW pg_roles AS
      SELECT
          rolname,
!         rolsuper,
!         rolinherit,
!         rolcreaterole,
!         rolcreatedb,
!         rolcatupdate,
!         rolcanlogin,
!         rolreplication,
          rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
-         rolbypassrls,
          setconfig as rolconfig,
          pg_authid.oid
      FROM pg_authid LEFT JOIN pg_db_role_setting s
--- 9,25 ----
  CREATE VIEW pg_roles AS
      SELECT
          rolname,
!         pg_check_role_attribute(pg_authid.oid, 'SUPERUSER') AS rolsuper,
!         pg_check_role_attribute(pg_authid.oid, 'INHERIT') AS rolinherit,
!         pg_check_role_attribute(pg_authid.oid, 'CREATEROLE') AS rolcreaterole,
!         pg_check_role_attribute(pg_authid.oid, 'CREATEDB') AS rolcreatedb,
!         pg_check_role_attribute(pg_authid.oid, 'CATUPDATE') AS rolcatupdate,
!         pg_check_role_attribute(pg_authid.oid, 'CANLOGIN') AS rolcanlogin,
!         pg_check_role_attribute(pg_authid.oid, 'REPLICATION') AS rolreplication,
!         pg_check_role_attribute(pg_authid.oid, 'BYPASSRLS') AS rolbypassrls,
          rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
          setconfig as rolconfig,
          pg_authid.oid
      FROM pg_authid LEFT JOIN pg_db_role_setting s
*************** CREATE VIEW pg_shadow AS
*** 29,44 ****
      SELECT
          rolname AS usename,
          pg_authid.oid AS usesysid,
!         rolcreatedb AS usecreatedb,
!         rolsuper AS usesuper,
!         rolcatupdate AS usecatupd,
!         rolreplication AS userepl,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          setconfig AS useconfig
      FROM pg_authid LEFT JOIN pg_db_role_setting s
      ON (pg_authid.oid = setrole AND setdatabase = 0)
!     WHERE rolcanlogin;
  
  REVOKE ALL on pg_shadow FROM public;
  
--- 29,44 ----
      SELECT
          rolname AS usename,
          pg_authid.oid AS usesysid,
!         pg_check_role_attribute(pg_authid.oid, 'CREATEDB') AS usecreatedb,
!         pg_check_role_attribute(pg_authid.oid, 'SUPERUSER') AS usesuper,
!         pg_check_role_attribute(pg_authid.oid, 'CATUPDATE') AS usecatupd,
!         pg_check_role_attribute(pg_authid.oid, 'REPLICATION') AS userepl,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          setconfig AS useconfig
      FROM pg_authid LEFT JOIN pg_db_role_setting s
      ON (pg_authid.oid = setrole AND setdatabase = 0)
!     WHERE pg_check_role_attribute(pg_authid.oid, 'CANLOGIN');
  
  REVOKE ALL on pg_shadow FROM public;
  
*************** CREATE VIEW pg_group AS
*** 48,54 ****
          oid AS grosysid,
          ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
      FROM pg_authid
!     WHERE NOT rolcanlogin;
  
  CREATE VIEW pg_user AS
      SELECT
--- 48,54 ----
          oid AS grosysid,
          ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
      FROM pg_authid
!     WHERE NOT pg_check_role_attribute(pg_authid.oid, 'CANLOGIN');
  
  CREATE VIEW pg_user AS
      SELECT
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
new file mode 100644
index 1a5244c..c079168
*** a/src/backend/commands/dbcommands.c
--- b/src/backend/commands/dbcommands.c
*************** static bool get_db_info(const char *name
*** 85,91 ****
  			Oid *dbLastSysOidP, TransactionId *dbFrozenXidP,
  			MultiXactId *dbMinMultiP,
  			Oid *dbTablespace, char **dbCollate, char **dbCtype);
- static bool have_createdb_privilege(void);
  static void remove_dbtablespaces(Oid db_id);
  static bool check_db_file_conflict(Oid db_id);
  static int	errdetail_busy_db(int notherbackends, int npreparedxacts);
--- 85,90 ----
*************** createdb(const CreatedbStmt *stmt)
*** 291,297 ****
  	 * "giveaway" attacks.  Note that a superuser will always have both of
  	 * these privileges a fortiori.
  	 */
! 	if (!have_createdb_privilege())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to create database")));
--- 290,296 ----
  	 * "giveaway" attacks.  Note that a superuser will always have both of
  	 * these privileges a fortiori.
  	 */
! 	if (!have_role_attribute(ROLE_ATTR_CREATEDB))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to create database")));
*************** RenameDatabase(const char *oldname, cons
*** 965,971 ****
  					   oldname);
  
  	/* must have createdb rights */
! 	if (!have_createdb_privilege())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to rename database")));
--- 964,970 ----
  					   oldname);
  
  	/* must have createdb rights */
! 	if (!have_role_attribute(ROLE_ATTR_CREATEDB))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to rename database")));
*************** AlterDatabaseOwner(const char *dbname, O
*** 1623,1629 ****
  		 * databases.  Because superusers will always have this right, we need
  		 * no special case for them.
  		 */
! 		if (!have_createdb_privilege())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				   errmsg("permission denied to change owner of database")));
--- 1622,1628 ----
  		 * databases.  Because superusers will always have this right, we need
  		 * no special case for them.
  		 */
! 		if (!have_role_attribute(ROLE_ATTR_CREATEDB))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				   errmsg("permission denied to change owner of database")));
*************** get_db_info(const char *name, LOCKMODE l
*** 1802,1827 ****
  	return result;
  }
  
- /* Check if current user has createdb privileges */
- static bool
- have_createdb_privilege(void)
- {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
- 	/* Superusers can always do everything */
- 	if (superuser())
- 		return true;
- 
- 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId()));
- 	if (HeapTupleIsValid(utup))
- 	{
- 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreatedb;
- 		ReleaseSysCache(utup);
- 	}
- 	return result;
- }
- 
  /*
   * Remove tablespace directories
   *
--- 1801,1806 ----
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
new file mode 100644
index 1a73fd8..a4a4d39
*** a/src/backend/commands/user.c
--- b/src/backend/commands/user.c
*************** static void DelRoleMems(const char *role
*** 56,69 ****
  			bool admin_opt);
  
  
- /* Check if current user has createrole privileges */
- static bool
- have_createrole_privilege(void)
- {
- 	return has_createrole_privilege(GetUserId());
- }
- 
- 
  /*
   * CREATE ROLE
   */
--- 56,61 ----
*************** CreateRole(CreateRoleStmt *stmt)
*** 88,93 ****
--- 80,86 ----
  	bool		canlogin = false;		/* Can this user login? */
  	bool		isreplication = false;	/* Is this a replication role? */
  	bool		bypassrls = false;		/* Is this a row security enabled role? */
+ 	RoleAttr	attributes = ROLE_ATTR_NONE;	/* role attributes, initialized to none. */
  	int			connlimit = -1; /* maximum connections allowed */
  	List	   *addroleto = NIL;	/* roles to make this a member of */
  	List	   *rolemembers = NIL;		/* roles to be members of this role */
*************** CreateRole(CreateRoleStmt *stmt)
*** 249,254 ****
--- 242,249 ----
  
  	if (dpassword && dpassword->arg)
  		password = strVal(dpassword->arg);
+ 
+ 	/* Role Attributes */
  	if (dissuper)
  		issuper = intVal(dissuper->arg) != 0;
  	if (dinherit)
*************** CreateRole(CreateRoleStmt *stmt)
*** 261,266 ****
--- 256,264 ----
  		canlogin = intVal(dcanlogin->arg) != 0;
  	if (disreplication)
  		isreplication = intVal(disreplication->arg) != 0;
+ 	if (dbypassRLS)
+ 		bypassrls = intVal(dbypassRLS->arg) != 0;
+ 
  	if (dconnlimit)
  	{
  		connlimit = intVal(dconnlimit->arg);
*************** CreateRole(CreateRoleStmt *stmt)
*** 277,284 ****
  		adminmembers = (List *) dadminmembers->arg;
  	if (dvalidUntil)
  		validUntil = strVal(dvalidUntil->arg);
- 	if (dbypassRLS)
- 		bypassrls = intVal(dbypassRLS->arg) != 0;
  
  	/* Check some permissions first */
  	if (issuper)
--- 275,280 ----
*************** CreateRole(CreateRoleStmt *stmt)
*** 304,310 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to create role")));
--- 300,306 ----
  	}
  	else
  	{
! 		if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to create role")));
*************** CreateRole(CreateRoleStmt *stmt)
*** 355,360 ****
--- 351,372 ----
  								validUntil_datum,
  								validUntil_null);
  
+ 	/* Set all role attributes */
+ 	if (issuper)
+ 		attributes |= ROLE_ATTR_SUPERUSER;
+ 	if (inherit)
+ 		attributes |= ROLE_ATTR_INHERIT;
+ 	if (createrole)
+ 		attributes |= ROLE_ATTR_CREATEROLE;
+ 	if (createdb)
+ 		attributes |= ROLE_ATTR_CREATEDB;
+ 	if (canlogin)
+ 		attributes |= ROLE_ATTR_CANLOGIN;
+ 	if (isreplication)
+ 		attributes |= ROLE_ATTR_REPLICATION;
+ 	if (bypassrls)
+ 		attributes |= ROLE_ATTR_BYPASSRLS;
+ 
  	/*
  	 * Build a tuple to insert
  	 */
*************** CreateRole(CreateRoleStmt *stmt)
*** 364,377 ****
  	new_record[Anum_pg_authid_rolname - 1] =
  		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
  
! 	new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
! 	new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
! 	new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
! 	new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
! 	/* superuser gets catupdate right by default */
! 	new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper);
! 	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
! 	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
  	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
  
  	if (password)
--- 376,383 ----
  	new_record[Anum_pg_authid_rolname - 1] =
  		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
  
! 	new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
! 
  	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
  
  	if (password)
*************** CreateRole(CreateRoleStmt *stmt)
*** 394,401 ****
  	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
  	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
  
- 	new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls);
- 
  	tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
  
  	/*
--- 400,405 ----
*************** AlterRole(AlterRoleStmt *stmt)
*** 508,513 ****
--- 512,518 ----
  	DefElem    *dvalidUntil = NULL;
  	DefElem    *dbypassRLS = NULL;
  	Oid			roleid;
+ 	RoleAttr attributes;
  
  	/* Extract options from the statement node tree */
  	foreach(option, stmt->options)
*************** AlterRole(AlterRoleStmt *stmt)
*** 661,688 ****
  	 * To mess with a superuser you gotta be superuser; else you need
  	 * createrole, or just want to change your own password
  	 */
! 	if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper || issuper >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter superusers")));
  	}
! 	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolreplication || isreplication >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter replication users")));
  	}
! 	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolbypassrls || bypassrls >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to change bypassrls attribute")));
  	}
! 	else if (!have_createrole_privilege())
  	{
  		if (!(inherit < 0 &&
  			  createrole < 0 &&
--- 666,696 ----
  	 * To mess with a superuser you gotta be superuser; else you need
  	 * createrole, or just want to change your own password
  	 */
! 
! 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 
! 	if ((attributes & ROLE_ATTR_SUPERUSER) || issuper >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter superusers")));
  	}
! 	else if ((attributes & ROLE_ATTR_REPLICATION) || isreplication >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter replication users")));
  	}
! 	else if ((attributes & ROLE_ATTR_BYPASSRLS) || bypassrls >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to change bypassrls attribute")));
  	}
! 	else if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
  	{
  		if (!(inherit < 0 &&
  			  createrole < 0 &&
*************** AlterRole(AlterRoleStmt *stmt)
*** 743,785 ****
  	 */
  	if (issuper >= 0)
  	{
! 		new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);
! 		new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
! 
! 		new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);
! 		new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true;
  	}
  
  	if (inherit >= 0)
  	{
! 		new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
! 		new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
  	}
  
  	if (createrole >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);
! 		new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
  	}
  
  	if (createdb >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0);
! 		new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true;
  	}
  
  	if (canlogin >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0);
! 		new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true;
  	}
  
  	if (isreplication >= 0)
  	{
! 		new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication > 0);
! 		new_record_repl[Anum_pg_authid_rolreplication - 1] = true;
  	}
  
  	if (dconnlimit)
  	{
  		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
--- 751,807 ----
  	 */
  	if (issuper >= 0)
  	{
! 		attributes = (issuper > 0) ? attributes | (ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE)
! 								   : attributes & ~(ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (inherit >= 0)
  	{
! 		attributes = (inherit > 0) ? attributes | ROLE_ATTR_INHERIT
! 								   : attributes & ~(ROLE_ATTR_INHERIT);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (createrole >= 0)
  	{
! 		attributes = (createrole > 0) ? attributes | ROLE_ATTR_CREATEROLE
! 									  : attributes & ~(ROLE_ATTR_CREATEROLE);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (createdb >= 0)
  	{
! 		attributes = (createdb > 0) ? attributes | ROLE_ATTR_CREATEDB
! 									: attributes & ~(ROLE_ATTR_CREATEDB);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (canlogin >= 0)
  	{
! 		attributes = (canlogin > 0) ? attributes | ROLE_ATTR_CANLOGIN
! 									: attributes & ~(ROLE_ATTR_CANLOGIN);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (isreplication >= 0)
  	{
! 		attributes = (isreplication > 0) ? attributes | ROLE_ATTR_REPLICATION
! 										 : attributes & ~(ROLE_ATTR_REPLICATION);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
! 	}
! 
! 	if (bypassrls >= 0)
! 	{
! 		attributes = (bypassrls > 0) ? attributes | ROLE_ATTR_BYPASSRLS
! 										 : attributes & ~(ROLE_ATTR_BYPASSRLS);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
+ 	/* If any role attributes were set, then update. */
+ 	if (new_record_repl[Anum_pg_authid_rolattr - 1])
+ 		new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
+ 
  	if (dconnlimit)
  	{
  		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
*************** AlterRole(AlterRoleStmt *stmt)
*** 815,825 ****
  	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
  	new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
  
- 	if (bypassrls >= 0)
- 	{
- 		new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls > 0);
- 		new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
- 	}
  
  	new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
  								  new_record_nulls, new_record_repl);
--- 837,842 ----
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 867,872 ****
--- 884,890 ----
  	HeapTuple	roletuple;
  	Oid			databaseid = InvalidOid;
  	Oid			roleid = InvalidOid;
+ 	RoleAttr	attributes;
  
  	if (stmt->role)
  	{
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 889,895 ****
  		 * To mess with a superuser you gotta be superuser; else you need
  		 * createrole, or just want to change your own settings
  		 */
! 		if (((Form_pg_authid) GETSTRUCT(roletuple))->rolsuper)
  		{
  			if (!superuser())
  				ereport(ERROR,
--- 907,914 ----
  		 * To mess with a superuser you gotta be superuser; else you need
  		 * createrole, or just want to change your own settings
  		 */
! 		attributes = ((Form_pg_authid) GETSTRUCT(roletuple))->rolattr;
! 		if (attributes & ROLE_ATTR_SUPERUSER)
  		{
  			if (!superuser())
  				ereport(ERROR,
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 898,904 ****
  		}
  		else
  		{
! 			if (!have_createrole_privilege() &&
  				HeapTupleGetOid(roletuple) != GetUserId())
  				ereport(ERROR,
  						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 917,923 ----
  		}
  		else
  		{
! 			if (!have_role_attribute(ROLE_ATTR_CREATEROLE) &&
  				HeapTupleGetOid(roletuple) != GetUserId())
  				ereport(ERROR,
  						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
*************** DropRole(DropRoleStmt *stmt)
*** 951,957 ****
  				pg_auth_members_rel;
  	ListCell   *item;
  
! 	if (!have_createrole_privilege())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to drop role")));
--- 970,976 ----
  				pg_auth_members_rel;
  	ListCell   *item;
  
! 	if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to drop role")));
*************** DropRole(DropRoleStmt *stmt)
*** 973,978 ****
--- 992,998 ----
  		char	   *detail_log;
  		SysScanDesc sscan;
  		Oid			roleid;
+ 		RoleAttr	attributes;
  
  		tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
  		if (!HeapTupleIsValid(tuple))
*************** DropRole(DropRoleStmt *stmt)
*** 1013,1020 ****
  		 * roles but not superuser roles.  This is mainly to avoid the
  		 * scenario where you accidentally drop the last superuser.
  		 */
! 		if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper &&
! 			!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to drop superusers")));
--- 1033,1040 ----
  		 * roles but not superuser roles.  This is mainly to avoid the
  		 * scenario where you accidentally drop the last superuser.
  		 */
! 		attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 		if ((attributes & ROLE_ATTR_SUPERUSER) && !superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to drop superusers")));
*************** RenameRole(const char *oldname, const ch
*** 1128,1133 ****
--- 1148,1154 ----
  	bool		repl_repl[Natts_pg_authid];
  	int			i;
  	Oid			roleid;
+ 	RoleAttr	attributes;
  
  	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
  	dsc = RelationGetDescr(rel);
*************** RenameRole(const char *oldname, const ch
*** 1173,1179 ****
  	/*
  	 * createrole is enough privilege unless you want to mess with a superuser
  	 */
! 	if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
  	{
  		if (!superuser())
  			ereport(ERROR,
--- 1194,1201 ----
  	/*
  	 * createrole is enough privilege unless you want to mess with a superuser
  	 */
! 	attributes = ((Form_pg_authid) GETSTRUCT(oldtuple))->rolattr;
! 	if (attributes & ROLE_ATTR_SUPERUSER)
  	{
  		if (!superuser())
  			ereport(ERROR,
*************** RenameRole(const char *oldname, const ch
*** 1182,1188 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to rename role")));
--- 1204,1210 ----
  	}
  	else
  	{
! 		if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to rename role")));
*************** AddRoleMems(const char *rolename, Oid ro
*** 1409,1415 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege() &&
  			!is_admin_of_role(grantorId, roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 1431,1437 ----
  	}
  	else
  	{
! 		if (!have_role_attribute(ROLE_ATTR_CREATEROLE) &&
  			!is_admin_of_role(grantorId, roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
*************** DelRoleMems(const char *rolename, Oid ro
*** 1555,1561 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege() &&
  			!is_admin_of_role(GetUserId(), roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 1577,1583 ----
  	}
  	else
  	{
! 		if (!have_role_attribute(ROLE_ATTR_CREATEROLE) &&
  			!is_admin_of_role(GetUserId(), roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
new file mode 100644
index 6ce8dae..491dc38
*** a/src/backend/commands/variable.c
--- b/src/backend/commands/variable.c
*************** check_session_authorization(char **newva
*** 776,781 ****
--- 776,782 ----
  	Oid			roleid;
  	bool		is_superuser;
  	role_auth_extra *myextra;
+ 	RoleAttr	attributes;
  
  	/* Do nothing for the boot_val default of NULL */
  	if (*newval == NULL)
*************** check_session_authorization(char **newva
*** 800,806 ****
  	}
  
  	roleid = HeapTupleGetOid(roleTup);
! 	is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
  
  	ReleaseSysCache(roleTup);
  
--- 801,808 ----
  	}
  
  	roleid = HeapTupleGetOid(roleTup);
! 	attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
! 	is_superuser = (attributes & ROLE_ATTR_SUPERUSER);
  
  	ReleaseSysCache(roleTup);
  
*************** check_role(char **newval, void **extra,
*** 844,849 ****
--- 846,852 ----
  	Oid			roleid;
  	bool		is_superuser;
  	role_auth_extra *myextra;
+ 	RoleAttr	attributes;
  
  	if (strcmp(*newval, "none") == 0)
  	{
*************** check_role(char **newval, void **extra,
*** 872,878 ****
  		}
  
  		roleid = HeapTupleGetOid(roleTup);
! 		is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
  
  		ReleaseSysCache(roleTup);
  
--- 875,882 ----
  		}
  
  		roleid = HeapTupleGetOid(roleTup);
! 		attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
! 		is_superuser = (attributes & ROLE_ATTR_SUPERUSER);
  
  		ReleaseSysCache(roleTup);
  
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
new file mode 100644
index 1977f09..5f1126e
*** a/src/backend/replication/logical/logicalfuncs.c
--- b/src/backend/replication/logical/logicalfuncs.c
***************
*** 23,34 ****
--- 23,36 ----
  
  #include "access/xlog_internal.h"
  
+ #include "catalog/pg_authid.h"
  #include "catalog/pg_type.h"
  
  #include "nodes/makefuncs.h"
  
  #include "mb/pg_wchar.h"
  
+ #include "utils/acl.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/inval.h"
*************** XLogRead(char *buf, TimeLineID tli, XLog
*** 205,211 ****
  static void
  check_permissions(void)
  {
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
--- 207,213 ----
  static void
  check_permissions(void)
  {
! 	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
new file mode 100644
index bd4701f..bc6a23a
*** a/src/backend/replication/slotfuncs.c
--- b/src/backend/replication/slotfuncs.c
***************
*** 17,32 ****
  #include "miscadmin.h"
  
  #include "access/htup_details.h"
  #include "replication/slot.h"
  #include "replication/logical.h"
  #include "replication/logicalfuncs.h"
  #include "utils/builtins.h"
  #include "utils/pg_lsn.h"
  
  static void
  check_permissions(void)
  {
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
--- 17,34 ----
  #include "miscadmin.h"
  
  #include "access/htup_details.h"
+ #include "catalog/pg_authid.h"
  #include "replication/slot.h"
  #include "replication/logical.h"
  #include "replication/logicalfuncs.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/pg_lsn.h"
  
  static void
  check_permissions(void)
  {
! 	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
new file mode 100644
index 6c232dc..58633cc
*** a/src/backend/rewrite/rowsecurity.c
--- b/src/backend/rewrite/rowsecurity.c
***************
*** 36,41 ****
--- 36,42 ----
  #include "access/heapam.h"
  #include "access/htup_details.h"
  #include "access/sysattr.h"
+ #include "catalog/pg_authid.h"
  #include "catalog/pg_class.h"
  #include "catalog/pg_inherits_fn.h"
  #include "catalog/pg_policy.h"
*************** check_enable_rls(Oid relid, Oid checkAsU
*** 521,527 ****
  	 */
  	if (!checkAsUser && row_security == ROW_SECURITY_OFF)
  	{
! 		if (has_bypassrls_privilege(user_id))
  			/* OK to bypass */
  			return RLS_NONE_ENV;
  		else
--- 522,528 ----
  	 */
  	if (!checkAsUser && row_security == ROW_SECURITY_OFF)
  	{
! 		if (has_role_attribute(user_id, ROLE_ATTR_BYPASSRLS))
  			/* OK to bypass */
  			return RLS_NONE_ENV;
  		else
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
new file mode 100644
index dc6eb2c..88525f4
*** a/src/backend/utils/adt/acl.c
--- b/src/backend/utils/adt/acl.c
*************** static Oid	convert_type_name(text *typen
*** 115,120 ****
--- 115,121 ----
  static AclMode convert_type_priv_string(text *priv_type_text);
  static AclMode convert_role_priv_string(text *priv_type_text);
  static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
+ static RoleAttr convert_role_attr_string(text *attr_type_text);
  
  static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue);
  
*************** pg_role_aclcheck(Oid role_oid, Oid rolei
*** 4602,4607 ****
--- 4603,4773 ----
  	return ACLCHECK_NO_PRIV;
  }
  
+ /*
+  * pg_has_role_attribute_id
+  *		Check that the role with the given oid has the given named role
+  *		attribute.
+  *
+  * Note: This function applies superuser checks.  Therefore, if the provided
+  * role is a superuser, then the result will always be true.
+  */
+ Datum
+ pg_has_role_attribute_id(PG_FUNCTION_ARGS)
+ {
+ 	Oid			roleoid = PG_GETARG_OID(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(has_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_has_role_attribute_name
+  *		Check that the named role has the given named role attribute.
+  *
+  * Note: This function applies superuser checks.  Therefore, if the provided
+  * role is a superuser, then the result will always be true.
+  */
+ Datum
+ pg_has_role_attribute_name(PG_FUNCTION_ARGS)
+ {
+ 	Name		rolename = PG_GETARG_NAME(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	Oid			roleoid;
+ 	RoleAttr	attribute;
+ 
+ 	roleoid = get_role_oid(NameStr(*rolename), false);
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(has_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_check_role_attribute_id
+  *		Check that the role with the given oid has the given named role
+  *		attribute.
+  *
+  * Note: This function is different from 'pg_has_role_attribute_id_attr' in that
+  * it does *not* apply any superuser checks.  Therefore, this function will
+  * always return the set value of the attribute, despite the superuser-ness of
+  * the provided role.
+  */
+ Datum
+ pg_check_role_attribute_id(PG_FUNCTION_ARGS)
+ {
+ 	Oid			roleoid = PG_GETARG_OID(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(check_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_check_role_attribute_name
+  *		Check that the named role has the given named role attribute.
+  *
+  * Note: This function is different from 'pg_has_role_attribute_name_attr' in
+  * that it does *not* apply any superuser checks.  Therefore, this function will
+  * always return the set value of the attribute, despite the superuser-ness of
+  * the provided role.
+  */
+ Datum
+ pg_check_role_attribute_name(PG_FUNCTION_ARGS)
+ {
+ 	Name		rolename = PG_GETARG_NAME(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	Oid			roleoid;
+ 	RoleAttr	attribute;
+ 
+ 	roleoid = get_role_oid(NameStr(*rolename), false);
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(check_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_all_role_attributes
+  *		Convert a RoleAttr representation of role attributes into an array of
+  *		corresponding text values.
+  *
+  * The first and only argument is a RoleAttr (int64) representation of the
+  * role attributes.
+  */
+ Datum
+ pg_all_role_attributes(PG_FUNCTION_ARGS)
+ {
+ 	RoleAttr		attributes = PG_GETARG_INT64(0);
+ 	Datum		   *temp_array;
+ 	ArrayType	   *result;
+ 	int				i = 0;
+ 
+ 	/*
+ 	 * If no attributes are assigned, then there is no need to go through the
+ 	 * individual checks for which are assigned.  Therefore, we can short circuit
+ 	 * and return an empty array.
+ 	 */
+ 	if (attributes == ROLE_ATTR_NONE)
+ 		PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));
+ 
+ 	temp_array = (Datum *) palloc(N_ROLE_ATTRIBUTES * sizeof(Datum));
+ 
+ 	/* Determine which attributes are assigned. */
+ 	if (attributes & ROLE_ATTR_SUPERUSER)
+ 		temp_array[i++] = CStringGetTextDatum("Superuser");
+ 	if (attributes & ROLE_ATTR_INHERIT)
+ 		temp_array[i++] = CStringGetTextDatum("Inherit");
+ 	if (attributes & ROLE_ATTR_CREATEROLE)
+ 		temp_array[i++] = CStringGetTextDatum("Create Role");
+ 	if (attributes & ROLE_ATTR_CREATEDB)
+ 		temp_array[i++] = CStringGetTextDatum("Create DB");
+ 	if (attributes & ROLE_ATTR_CATUPDATE)
+ 		temp_array[i++] = CStringGetTextDatum("Catalog Update");
+ 	if (attributes & ROLE_ATTR_CANLOGIN)
+ 		temp_array[i++] = CStringGetTextDatum("Login");
+ 	if (attributes & ROLE_ATTR_REPLICATION)
+ 		temp_array[i++] = CStringGetTextDatum("Replication");
+ 	if (attributes & ROLE_ATTR_BYPASSRLS)
+ 		temp_array[i++] = CStringGetTextDatum("Bypass RLS");
+ 
+ 	result = construct_array(temp_array, i, TEXTOID, -1, false, 'i');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * convert_role_attr_string
+  *		Convert text string to RoleAttr value.
+  */
+ static RoleAttr
+ convert_role_attr_string(text *attr_type_text)
+ {
+ 	char	   *attr_type = text_to_cstring(attr_type_text);
+ 
+ 	if (pg_strcasecmp(attr_type, "SUPERUSER") == 0)
+ 		return ROLE_ATTR_SUPERUSER;
+ 	else if (pg_strcasecmp(attr_type, "INHERIT") == 0)
+ 		return ROLE_ATTR_INHERIT;
+ 	else if (pg_strcasecmp(attr_type, "CREATEROLE") == 0)
+ 		return ROLE_ATTR_CREATEROLE;
+ 	else if (pg_strcasecmp(attr_type, "CREATEDB") == 0)
+ 		return ROLE_ATTR_CREATEDB;
+ 	else if (pg_strcasecmp(attr_type, "CATUPDATE") == 0)
+ 		return ROLE_ATTR_CATUPDATE;
+ 	else if (pg_strcasecmp(attr_type, "CANLOGIN") == 0)
+ 		return ROLE_ATTR_CANLOGIN;
+ 	else if (pg_strcasecmp(attr_type, "REPLICATION") == 0)
+ 		return ROLE_ATTR_REPLICATION;
+ 	else if (pg_strcasecmp(attr_type, "BYPASSRLS") == 0)
+ 		return ROLE_ATTR_BYPASSRLS;
+ 	else
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("unrecognized role attribute: \"%s\"", attr_type)));
+ }
  
  /*
   * initialization function (called by InitPostgres)
*************** RoleMembershipCacheCallback(Datum arg, i
*** 4634,4656 ****
  }
  
  
- /* Check if specified role has rolinherit set */
- static bool
- has_rolinherit(Oid roleid)
- {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
- 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (HeapTupleIsValid(utup))
- 	{
- 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
- 		ReleaseSysCache(utup);
- 	}
- 	return result;
- }
- 
- 
  /*
   * Get a list of roles that the specified roleid has the privileges of
   *
--- 4800,4805 ----
*************** roles_has_privs_of(Oid roleid)
*** 4697,4703 ****
  		int			i;
  
  		/* Ignore non-inheriting roles */
! 		if (!has_rolinherit(memberid))
  			continue;
  
  		/* Find roles that memberid is directly a member of */
--- 4846,4852 ----
  		int			i;
  
  		/* Ignore non-inheriting roles */
! 		if (!has_role_attribute(memberid, ROLE_ATTR_INHERIT))
  			continue;
  
  		/* Find roles that memberid is directly a member of */
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
new file mode 100644
index 2f02303..c0d0718
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
***************
*** 33,38 ****
--- 33,39 ----
  #include "access/htup_details.h"
  #include "access/sysattr.h"
  #include "access/xact.h"
+ #include "catalog/pg_authid.h"
  #include "catalog/pg_collation.h"
  #include "catalog/pg_constraint.h"
  #include "catalog/pg_operator.h"
***************
*** 43,48 ****
--- 44,50 ----
  #include "parser/parse_coerce.h"
  #include "parser/parse_relation.h"
  #include "miscadmin.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
*************** RI_Initial_Check(Trigger *trigger, Relat
*** 2308,2314 ****
  	 * bypassrls right or is the table owner of the table(s) involved which
  	 * have RLS enabled.
  	 */
! 	if (!has_bypassrls_privilege(GetUserId()) &&
  		((pk_rel->rd_rel->relrowsecurity &&
  		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
  		 (fk_rel->rd_rel->relrowsecurity &&
--- 2310,2316 ----
  	 * bypassrls right or is the table owner of the table(s) involved which
  	 * have RLS enabled.
  	 */
! 	if (!have_role_attribute(ROLE_ATTR_BYPASSRLS) &&
  		((pk_rel->rd_rel->relrowsecurity &&
  		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
  		 (fk_rel->rd_rel->relrowsecurity &&
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
new file mode 100644
index 8fccb4c..db2a0fb
*** a/src/backend/utils/init/miscinit.c
--- b/src/backend/utils/init/miscinit.c
***************
*** 40,45 ****
--- 40,46 ----
  #include "storage/pg_shmem.h"
  #include "storage/proc.h"
  #include "storage/procarray.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/guc.h"
  #include "utils/memutils.h"
*************** SetUserIdAndContext(Oid userid, bool sec
*** 329,352 ****
  
  
  /*
-  * Check whether specified role has explicit REPLICATION privilege
-  */
- bool
- has_rolreplication(Oid roleid)
- {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
- 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (HeapTupleIsValid(utup))
- 	{
- 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication;
- 		ReleaseSysCache(utup);
- 	}
- 	return result;
- }
- 
- /*
   * Initialize user identity during normal backend startup
   */
  void
--- 330,335 ----
*************** InitializeSessionUserId(const char *role
*** 375,381 ****
  	roleid = HeapTupleGetOid(roleTup);
  
  	AuthenticatedUserId = roleid;
! 	AuthenticatedUserIsSuperuser = rform->rolsuper;
  
  	/* This sets OuterUserId/CurrentUserId too */
  	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
--- 358,364 ----
  	roleid = HeapTupleGetOid(roleTup);
  
  	AuthenticatedUserId = roleid;
! 	AuthenticatedUserIsSuperuser = (rform->rolattr & ROLE_ATTR_SUPERUSER);
  
  	/* This sets OuterUserId/CurrentUserId too */
  	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
*************** InitializeSessionUserId(const char *role
*** 394,400 ****
  		/*
  		 * Is role allowed to login at all?
  		 */
! 		if (!rform->rolcanlogin)
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  					 errmsg("role \"%s\" is not permitted to log in",
--- 377,383 ----
  		/*
  		 * Is role allowed to login at all?
  		 */
! 		if (!(rform->rolattr & ROLE_ATTR_CANLOGIN))
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  					 errmsg("role \"%s\" is not permitted to log in",
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
new file mode 100644
index c348034..268001f
*** a/src/backend/utils/init/postinit.c
--- b/src/backend/utils/init/postinit.c
*************** InitPostgres(const char *in_dbname, Oid
*** 762,768 ****
  	{
  		Assert(!bootstrap);
  
! 		if (!superuser() && !has_rolreplication(GetUserId()))
  			ereport(FATAL,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser or replication role to start walsender")));
--- 762,768 ----
  	{
  		Assert(!bootstrap);
  
! 		if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  			ereport(FATAL,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser or replication role to start walsender")));
diff --git a/src/backend/utils/misc/superuser.c b/src/backend/utils/misc/superuser.c
new file mode 100644
index ff0f947..9af77ed
*** a/src/backend/utils/misc/superuser.c
--- b/src/backend/utils/misc/superuser.c
***************
*** 22,27 ****
--- 22,28 ----
  
  #include "access/htup_details.h"
  #include "catalog/pg_authid.h"
+ #include "utils/acl.h"
  #include "utils/inval.h"
  #include "utils/syscache.h"
  #include "miscadmin.h"
*************** superuser_arg(Oid roleid)
*** 58,63 ****
--- 59,65 ----
  {
  	bool		result;
  	HeapTuple	rtup;
+ 	RoleAttr	attributes;
  
  	/* Quick out for cache hit */
  	if (OidIsValid(last_roleid) && last_roleid == roleid)
*************** superuser_arg(Oid roleid)
*** 71,77 ****
  	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(rtup))
  	{
! 		result = ((Form_pg_authid) GETSTRUCT(rtup))->rolsuper;
  		ReleaseSysCache(rtup);
  	}
  	else
--- 73,80 ----
  	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(rtup))
  	{
! 		attributes = ((Form_pg_authid) GETSTRUCT(rtup))->rolattr;
! 		result = (attributes & ROLE_ATTR_SUPERUSER);
  		ReleaseSysCache(rtup);
  	}
  	else
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
new file mode 100644
index eb633bc..f638167
*** a/src/bin/pg_dump/pg_dumpall.c
--- b/src/bin/pg_dump/pg_dumpall.c
*************** dumpRoles(PGconn *conn)
*** 671,680 ****
  	/* note: rolconfig is dumped later */
  	if (server_version >= 90500)
  		printfPQExpBuffer(buf,
! 						  "SELECT oid, rolname, rolsuper, rolinherit, "
! 						  "rolcreaterole, rolcreatedb, "
! 						  "rolcanlogin, rolconnlimit, rolpassword, "
! 						  "rolvaliduntil, rolreplication, rolbypassrls, "
  			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
  						  "rolname = current_user AS is_current_user "
  						  "FROM pg_authid "
--- 671,686 ----
  	/* note: rolconfig is dumped later */
  	if (server_version >= 90500)
  		printfPQExpBuffer(buf,
! 						  "SELECT oid, rolname, "
! 						  "pg_check_role_attribute(oid, 'SUPERUSER') AS rolsuper, "
! 						  "pg_check_role_attribute(oid, 'INHERIT') AS rolinherit, "
! 						  "pg_check_role_attribute(oid, 'CREATEROLE') AS rolcreaterole, "
! 						  "pg_check_role_attribute(oid, 'CREATEDB') AS rolcreatedb, "
! 						  "pg_check_role_attribute(oid, 'CANLOGIN') AS rolcanlogin, "
! 						  "pg_check_role_attribute(oid, 'REPLICATION') AS rolreplication, "
! 						  "pg_check_role_attribute(oid, 'BYPASSRLS') AS rolbypassrls, "
! 						  "rolconnlimit, rolpassword, "
! 						  "rolvaliduntil, "
  			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
  						  "rolname = current_user AS is_current_user "
  						  "FROM pg_authid "
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
new file mode 100644
index 3b63d2b..f28d9f4
*** a/src/include/catalog/pg_authid.h
--- b/src/include/catalog/pg_authid.h
***************
*** 45,60 ****
  CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
  {
  	NameData	rolname;		/* name of role */
! 	bool		rolsuper;		/* read this field via superuser() only! */
! 	bool		rolinherit;		/* inherit privileges from other roles? */
! 	bool		rolcreaterole;	/* allowed to create more roles? */
! 	bool		rolcreatedb;	/* allowed to create databases? */
! 	bool		rolcatupdate;	/* allowed to alter catalogs manually? */
! 	bool		rolcanlogin;	/* allowed to log in as session user? */
! 	bool		rolreplication; /* role used for streaming replication */
! 	bool		rolbypassrls;	/* allowed to bypass row level security? */
  	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
- 
  	/* remaining fields may be null; use heap_getattr to read them! */
  	text		rolpassword;	/* password, if any */
  	timestamptz rolvaliduntil;	/* password expiration time, if any */
--- 45,52 ----
  CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
  {
  	NameData	rolname;		/* name of role */
! 	int64		rolattr;		/* role attribute bitmask */
  	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
  	/* remaining fields may be null; use heap_getattr to read them! */
  	text		rolpassword;	/* password, if any */
  	timestamptz rolvaliduntil;	/* password expiration time, if any */
*************** typedef FormData_pg_authid *Form_pg_auth
*** 74,101 ****
   *		compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid					12
  #define Anum_pg_authid_rolname			1
! #define Anum_pg_authid_rolsuper			2
! #define Anum_pg_authid_rolinherit		3
! #define Anum_pg_authid_rolcreaterole	4
! #define Anum_pg_authid_rolcreatedb		5
! #define Anum_pg_authid_rolcatupdate		6
! #define Anum_pg_authid_rolcanlogin		7
! #define Anum_pg_authid_rolreplication	8
! #define Anum_pg_authid_rolbypassrls		9
! #define Anum_pg_authid_rolconnlimit		10
! #define Anum_pg_authid_rolpassword		11
! #define Anum_pg_authid_rolvaliduntil	12
  
  /* ----------------
   *		initial contents of pg_authid
   *
   * The uppercase quantities will be replaced at initdb time with
   * user choices.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t -1 _null_ _null_));
  
  #define BOOTSTRAP_SUPERUSERID 10
  
--- 66,118 ----
   *		compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid					5
  #define Anum_pg_authid_rolname			1
! #define Anum_pg_authid_rolattr			2
! #define Anum_pg_authid_rolconnlimit		3
! #define Anum_pg_authid_rolpassword		4
! #define Anum_pg_authid_rolvaliduntil	5
! 
! /* ----------------
!  * Role attributes are encoded so that we can OR them together in a bitmask.
!  * The present representation of RoleAttr (defined in acl.h) limits us to 64
!  * distinct rights.
!  * ----------------
!  */
! #define ROLE_ATTR_SUPERUSER		(1<<0)
! #define ROLE_ATTR_INHERIT		(1<<1)
! #define ROLE_ATTR_CREATEROLE	(1<<2)
! #define ROLE_ATTR_CREATEDB		(1<<3)
! #define ROLE_ATTR_CATUPDATE		(1<<4)
! #define ROLE_ATTR_CANLOGIN		(1<<5)
! #define ROLE_ATTR_REPLICATION	(1<<6)
! #define ROLE_ATTR_BYPASSRLS		(1<<7)
! #define N_ROLE_ATTRIBUTES		8		/* 1 plus the last 1<<x */
! #define ROLE_ATTR_NONE			0
! 
! /* ----------------
!  * All currently available attributes.
!  *
!  * Note: This value is currently used by genbki.pl.  Unfortunately, we have to
!  * hard code this value as we cannot use an approach like (1 << N_ROLE_ATTRIBUTES) - 1
!  * as genbki.pl simply uses the literal value associated with the #define symbol
!  * which causes an incorrect substitution. Therefore, whenever new role attributes
!  * are added this value MUST be changed as well.
!  * ----------------
!  */
! #define ROLE_ATTR_ALL          255 /* equals (1 << N_ROLE_ATTRIBUTES) - 1 */
  
  /* ----------------
   *		initial contents of pg_authid
   *
   * The uppercase quantities will be replaced at initdb time with
   * user choices.
+  *
+  * PGROLATTRALL is substituted by genbki.pl to use the value defined by
+  * ROLE_ATTR_ALL.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" PGROLATTRALL -1 _null_ _null_));
  
  #define BOOTSTRAP_SUPERUSERID 10
  
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index deba4b1..ba3b9ec
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("rank of hypothetical row without
*** 5100,5105 ****
--- 5100,5116 ----
  DATA(insert OID = 3993 ( dense_rank_final	PGNSP PGUID 12 1 0 2276 0 f f f f f f i 2 0 20 "2281 2276" "{2281,2276}" "{i,v}" _null_ _null_	hypothetical_dense_rank_final _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  
+ /* role attribute support functions */
+ DATA(insert OID = 3994 ( pg_has_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ pg_has_role_attribute_id _null_ _null_ _null_ ));
+ DESCR("check role attribute by role oid with superuser bypass check");
+ DATA(insert OID = 3995 ( pg_has_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "19 25" _null_ _null_ _null_ _null_ pg_has_role_attribute_name _null_ _null_ _null_ ));
+ DESCR("check role attribute by role name with superuser bypass check");
+ DATA(insert OID = 3996 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_id _null_ _null_ _null_ ));
+ DESCR("check role attribute by role id");
+ DATA(insert OID = 3997 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "19 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_name _null_ _null_ _null_ ));
+ DESCR("check role attribute by role name");
+ DATA(insert OID = 3998 ( pg_all_role_attributes		PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 1009 "20" _null_ _null_ _null_ _null_ pg_all_role_attributes _null_ _null_ _null_));
+ DESCR("convert role attributes to string array");
  
  /*
   * Symbolic values for provolatile column: these indicate whether the result
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
new file mode 100644
index a8e3164..1687633
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
*************** typedef enum AclObjectKind
*** 200,205 ****
--- 200,206 ----
  	MAX_ACL_KIND				/* MUST BE LAST */
  } AclObjectKind;
  
+ typedef uint64 RoleAttr;		/* a bitmask for role attribute bits */
  
  /*
   * routines used internally
*************** extern bool pg_foreign_data_wrapper_owne
*** 326,332 ****
  extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
  extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
  extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
! extern bool has_createrole_privilege(Oid roleid);
! extern bool has_bypassrls_privilege(Oid roleid);
  
  #endif   /* ACL_H */
--- 327,336 ----
  extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
  extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
  extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
! 
! /* role attribute check routines */
! extern bool has_role_attribute(Oid roleid, RoleAttr attribute);
! extern bool have_role_attribute(RoleAttr attribute);
! extern bool check_role_attribute(Oid roleid, RoleAttr attribute);
  
  #endif   /* ACL_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index 565cff3..a32b84c
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum pg_has_role_id_name(PG_FUNC
*** 106,111 ****
--- 106,116 ----
  extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS);
  extern Datum pg_has_role_name(PG_FUNCTION_ARGS);
  extern Datum pg_has_role_id(PG_FUNCTION_ARGS);
+ extern Datum pg_has_role_attribute_id(PG_FUNCTION_ARGS);
+ extern Datum pg_has_role_attribute_name(PG_FUNCTION_ARGS);
+ extern Datum pg_check_role_attribute_id(PG_FUNCTION_ARGS);
+ extern Datum pg_check_role_attribute_name(PG_FUNCTION_ARGS);
+ extern Datum pg_all_role_attributes(PG_FUNCTION_ARGS);
  
  /* bool.c */
  extern Datum boolin(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
new file mode 100644
index 80c3351..cec0b61
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
*************** pg_group| SELECT pg_authid.rolname AS gr
*** 1314,1320 ****
             FROM pg_auth_members
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
!   WHERE (NOT pg_authid.rolcanlogin);
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,
--- 1314,1320 ----
             FROM pg_auth_members
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
!   WHERE (NOT pg_check_role_attribute(pg_authid.oid, 'CANLOGIN'::text));
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,
*************** pg_replication_slots| SELECT l.slot_name
*** 1405,1421 ****
     FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, xmin, catalog_xmin, restart_lsn)
       LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
  pg_roles| SELECT pg_authid.rolname,
!     pg_authid.rolsuper,
!     pg_authid.rolinherit,
!     pg_authid.rolcreaterole,
!     pg_authid.rolcreatedb,
!     pg_authid.rolcatupdate,
!     pg_authid.rolcanlogin,
!     pg_authid.rolreplication,
      pg_authid.rolconnlimit,
      '********'::text AS rolpassword,
      pg_authid.rolvaliduntil,
-     pg_authid.rolbypassrls,
      s.setconfig AS rolconfig,
      pg_authid.oid
     FROM (pg_authid
--- 1405,1421 ----
     FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, xmin, catalog_xmin, restart_lsn)
       LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
  pg_roles| SELECT pg_authid.rolname,
!     pg_check_role_attribute(pg_authid.oid, 'SUPERUSER'::text) AS rolsuper,
!     pg_check_role_attribute(pg_authid.oid, 'INHERIT'::text) AS rolinherit,
!     pg_check_role_attribute(pg_authid.oid, 'CREATEROLE'::text) AS rolcreaterole,
!     pg_check_role_attribute(pg_authid.oid, 'CREATEDB'::text) AS rolcreatedb,
!     pg_check_role_attribute(pg_authid.oid, 'CATUPDATE'::text) AS rolcatupdate,
!     pg_check_role_attribute(pg_authid.oid, 'CANLOGIN'::text) AS rolcanlogin,
!     pg_check_role_attribute(pg_authid.oid, 'REPLICATION'::text) AS rolreplication,
!     pg_check_role_attribute(pg_authid.oid, 'BYPASSRLS'::text) AS rolbypassrls,
      pg_authid.rolconnlimit,
      '********'::text AS rolpassword,
      pg_authid.rolvaliduntil,
      s.setconfig AS rolconfig,
      pg_authid.oid
     FROM (pg_authid
*************** pg_settings| SELECT a.name,
*** 1608,1623 ****
     FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline);
  pg_shadow| SELECT pg_authid.rolname AS usename,
      pg_authid.oid AS usesysid,
!     pg_authid.rolcreatedb AS usecreatedb,
!     pg_authid.rolsuper AS usesuper,
!     pg_authid.rolcatupdate AS usecatupd,
!     pg_authid.rolreplication AS userepl,
      pg_authid.rolpassword AS passwd,
      (pg_authid.rolvaliduntil)::abstime AS valuntil,
      s.setconfig AS useconfig
     FROM (pg_authid
       LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
!   WHERE pg_authid.rolcanlogin;
  pg_stat_activity| SELECT s.datid,
      d.datname,
      s.pid,
--- 1608,1623 ----
     FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline);
  pg_shadow| SELECT pg_authid.rolname AS usename,
      pg_authid.oid AS usesysid,
!     pg_check_role_attribute(pg_authid.oid, 'CREATEDB'::text) AS usecreatedb,
!     pg_check_role_attribute(pg_authid.oid, 'SUPERUSER'::text) AS usesuper,
!     pg_check_role_attribute(pg_authid.oid, 'CATUPDATE'::text) AS usecatupd,
!     pg_check_role_attribute(pg_authid.oid, 'REPLICATION'::text) AS userepl,
      pg_authid.rolpassword AS passwd,
      (pg_authid.rolvaliduntil)::abstime AS valuntil,
      s.setconfig AS useconfig
     FROM (pg_authid
       LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
!   WHERE pg_check_role_attribute(pg_authid.oid, 'CANLOGIN'::text);
  pg_stat_activity| SELECT s.datid,
      d.datname,
      s.pid,
#19Stephen Frost
sfrost@snowman.net
In reply to: Adam Brightwell (#18)
Re: Role Attribute Bitmask Catalog Representation

Adam,

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

Attached is an updated patch.

Awesome, thanks!

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
*************** pg_extension_ownercheck(Oid ext_oid, Oid
*** 5051,5102 ****
}

/*
! * Check whether specified role has CREATEROLE privilege (or is a superuser)
*
! * Note: roles do not have owners per se; instead we use this test in
! * places where an ownership-like permissions test is needed for a role.
! * Be sure to apply it to the role trying to do the operation, not the
! * role being operated on! Also note that this generally should not be
! * considered enough privilege if the target role is a superuser.
! * (We don't handle that consideration here because we want to give a
! * separate error message for such cases, so the caller has to deal with it.)
*/

The comment above is pretty big and I don't think we want to completely
remove it. Can you add it as another 'Note' in the 'has_role_attribute'
comment and reword it accordingly?

*************** AlterRole(AlterRoleStmt *stmt)
*** 508,513 ****
--- 512,518 ----
DefElem    *dvalidUntil = NULL;
DefElem    *dbypassRLS = NULL;
Oid			roleid;
+ 	RoleAttr attributes;

Whitespace issue that should be fixed- attributes should line up with
roleid.

--- 1405,1421 ----
FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, xmin, catalog_xmin, restart_lsn)
LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
pg_roles| SELECT pg_authid.rolname,
!     pg_check_role_attribute(pg_authid.oid, 'SUPERUSER'::text) AS rolsuper,
!     pg_check_role_attribute(pg_authid.oid, 'INHERIT'::text) AS rolinherit,
!     pg_check_role_attribute(pg_authid.oid, 'CREATEROLE'::text) AS rolcreaterole,
!     pg_check_role_attribute(pg_authid.oid, 'CREATEDB'::text) AS rolcreatedb,
!     pg_check_role_attribute(pg_authid.oid, 'CATUPDATE'::text) AS rolcatupdate,
!     pg_check_role_attribute(pg_authid.oid, 'CANLOGIN'::text) AS rolcanlogin,
!     pg_check_role_attribute(pg_authid.oid, 'REPLICATION'::text) AS rolreplication,
!     pg_check_role_attribute(pg_authid.oid, 'BYPASSRLS'::text) AS rolbypassrls,
pg_authid.rolconnlimit,
'********'::text AS rolpassword,
pg_authid.rolvaliduntil,
s.setconfig AS rolconfig,
pg_authid.oid
FROM (pg_authid

It occurs to me that in this case (and a few others..), we're doing a
lot of extra work- each call to pg_check_role_attribute() is doing a
lookup on the oid just to get back to what the rolattr value on this row
is. How about a function which takes rolattr and the text
representation of the attribute and returns if the bit is set for that
rolattr value? Then you'd pass pg_authid.rolattr into the function
calls above instead of the role's oid.

I don't see any changes to the regression test files, were they
forgotten in the patch? I would think that at least the view definition
changes would require updates to the regression tests, though perhaps
nothing else.

Overall, I'm pretty happy with the patch and would suggest moving on to
writing up the documentation changes to go along with the code changes.
I'll continue to play around with it but it all seems pretty clean to
me and will allow us to easily add the additiaonl role attributes being
discussed.

Thanks!

Stephen

#20Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Stephen Frost (#19)
Re: Role Attribute Bitmask Catalog Representation

Stephen,

The comment above is pretty big and I don't think we want to completely

remove it. Can you add it as another 'Note' in the 'has_role_attribute'
comment and reword it accordingly?

Ok, agreed. Will address.

Whitespace issue that should be fixed- attributes should line up with

roleid.

Ok. Will address.

It occurs to me that in this case (and a few others..), we're doing a

lot of extra work- each call to pg_check_role_attribute() is doing a
lookup on the oid just to get back to what the rolattr value on this row
is. How about a function which takes rolattr and the text
representation of the attribute and returns if the bit is set for that
rolattr value? Then you'd pass pg_authid.rolattr into the function
calls above instead of the role's oid.

Makes sense, I'll put that together.

I don't see any changes to the regression test files, were they
forgotten in the patch? I would think that at least the view definition
changes would require updates to the regression tests, though perhaps
nothing else.

Hmmm... :-/ The regression tests that changed were in
'src/test/regress/expected/rules.out' and should be near the bottom of the
patch.

Overall, I'm pretty happy with the patch and would suggest moving on to
writing up the documentation changes to go along with the code changes.
I'll continue to play around with it but it all seems pretty clean to
me and will allow us to easily add the additiaonl role attributes being
discussed.

Sounds good. I'll start on those changes next.

Thanks,
Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

#21Stephen Frost
sfrost@snowman.net
In reply to: Adam Brightwell (#20)
Re: Role Attribute Bitmask Catalog Representation

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

I don't see any changes to the regression test files, were they
forgotten in the patch? I would think that at least the view definition
changes would require updates to the regression tests, though perhaps
nothing else.

Hmmm... :-/ The regression tests that changed were in
'src/test/regress/expected/rules.out' and should be near the bottom of the
patch.

Hah, looked just like changes to the system_views, sorry for the
confusion. :)

Overall, I'm pretty happy with the patch and would suggest moving on to
writing up the documentation changes to go along with the code changes.
I'll continue to play around with it but it all seems pretty clean to
me and will allow us to easily add the additiaonl role attributes being
discussed.

Sounds good. I'll start on those changes next.

Great!

Thanks,

Stephen

#22Michael Paquier
michael.paquier@gmail.com
In reply to: Stephen Frost (#21)
Re: Role Attribute Bitmask Catalog Representation

On Sun, Dec 7, 2014 at 1:50 AM, Stephen Frost <sfrost@snowman.net> wrote:

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

I don't see any changes to the regression test files, were they
forgotten in the patch? I would think that at least the view definition
changes would require updates to the regression tests, though perhaps
nothing else.

Hmmm... :-/ The regression tests that changed were in
'src/test/regress/expected/rules.out' and should be near the bottom of the
patch.

Hah, looked just like changes to the system_views, sorry for the
confusion. :)

Overall, I'm pretty happy with the patch and would suggest moving on to
writing up the documentation changes to go along with the code changes.
I'll continue to play around with it but it all seems pretty clean to
me and will allow us to easily add the additiaonl role attributes being
discussed.

Sounds good. I'll start on those changes next.

This patch (https://commitfest.postgresql.org/action/patch_view?id=1616)
has not been updated in the commitfest app for two months, making its
progress hard to track. However, even if some progress has been made
things are still not complete (documentation, etc.). Opinions about
marking that as returned with feedback for the current commit fest and
create a new entry for the next CF if this work is going to be
pursued?
I guess that it would be fine simply move it to the next CF, but it
seems I cannot do that myself in the CF app.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Michael Paquier (#22)
Re: Role Attribute Bitmask Catalog Representation

Michael,

This patch (https://commitfest.postgresql.org/action/patch_view?id=1616)

has not been updated in the commitfest app for two months, making its
progress hard to track.

I believe that the mentioned patch should be considered 'on hold' or
'dependent' upon the acceptance of the work that is being done in this
thread.

Also, the changes proposed by this thread have already been added to the
next commitfest (https://commitfest.postgresql.org/action/patch_view?id=1651
).

However, even if some progress has been made

things are still not complete (documentation, etc.). Opinions about
marking that as returned with feedback for the current commit fest and
create a new entry for the next CF if this work is going to be
pursued?

I guess that it would be fine simply move it to the next CF, but it

seems I cannot do that myself in the CF app.

This work will certainly continue to be pursued. If a simple move is
possible/acceptable, then I think that would be the best option. I can
handle that as it would appear that I am capable of moving it, if that is
acceptable.

Thanks,
Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

#24Michael Paquier
michael.paquier@gmail.com
In reply to: Adam Brightwell (#23)
Re: Role Attribute Bitmask Catalog Representation

On Mon, Dec 8, 2014 at 12:34 PM, Adam Brightwell
<adam.brightwell@crunchydatasolutions.com> wrote:

Michael,

This patch (https://commitfest.postgresql.org/action/patch_view?id=1616)
has not been updated in the commitfest app for two months, making its
progress hard to track.

I believe that the mentioned patch should be considered 'on hold' or
'dependent' upon the acceptance of the work that is being done in this
thread.

Also, the changes proposed by this thread have already been added to the
next commitfest
(https://commitfest.postgresql.org/action/patch_view?id=1651).

However, even if some progress has been made
things are still not complete (documentation, etc.). Opinions about
marking that as returned with feedback for the current commit fest and
create a new entry for the next CF if this work is going to be
pursued?

I guess that it would be fine simply move it to the next CF, but it
seems I cannot do that myself in the CF app.

This work will certainly continue to be pursued. If a simple move is
possible/acceptable, then I think that would be the best option. I can
handle that as it would appear that I am capable of moving it, if that is
acceptable.

Yes please. Actually I could have done it, just found the option to do
so. Let's see what shows up with your work.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Michael Paquier (#24)
Re: Role Attribute Bitmask Catalog Representation

Michael,

This work will certainly continue to be pursued. If a simple move is
possible/acceptable, then I think that would be the best option. I can
handle that as it would appear that I am capable of moving it, if that is
acceptable.

Yes please. Actually I could have done it, just found the option to do
so. Let's see what shows up with your work.

I have moved it to commitfest 2014-12 and marked as "Waiting on Author" if
that is acceptable.

Thanks,
Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

#26Michael Paquier
michael.paquier@gmail.com
In reply to: Adam Brightwell (#25)
Re: Role Attribute Bitmask Catalog Representation

On Tue, Dec 9, 2014 at 12:10 AM, Adam Brightwell
<adam.brightwell@crunchydatasolutions.com> wrote:

Michael,

This work will certainly continue to be pursued. If a simple move is
possible/acceptable, then I think that would be the best option. I can
handle that as it would appear that I am capable of moving it, if that
is
acceptable.

Yes please. Actually I could have done it, just found the option to do
so. Let's see what shows up with your work.

I have moved it to commitfest 2014-12 and marked as "Waiting on Author" if
that is acceptable.

Thanks! I guess that's fine.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Stephen Frost (#19)
1 attachment(s)
Re: Role Attribute Bitmask Catalog Representation

All,

Overall, I'm pretty happy with the patch and would suggest moving on to

writing up the documentation changes to go along with the code changes.
I'll continue to play around with it but it all seems pretty clean to
me and will allow us to easily add the additiaonl role attributes being
discussed.

I have attached an updated patch with initial documentation changes for
review.

Thanks,
Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

Attachments:

role-attribute-bitmask-v5.patchtext/x-patch; charset=US-ASCII; name=role-attribute-bitmask-v5.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
new file mode 100644
index 9ceb96b..b0b4fca
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 1391,1441 ****
       </row>
  
       <row>
!       <entry><structfield>rolsuper</structfield></entry>
!       <entry><type>bool</type></entry>
        <entry>Role has superuser privileges</entry>
       </row>
  
       <row>
!       <entry><structfield>rolinherit</structfield></entry>
!       <entry><type>bool</type></entry>
!       <entry>Role automatically inherits privileges of roles it is a
!        member of</entry>
       </row>
  
       <row>
!       <entry><structfield>rolcreaterole</structfield></entry>
!       <entry><type>bool</type></entry>
        <entry>Role can create more roles</entry>
       </row>
  
       <row>
!       <entry><structfield>rolcreatedb</structfield></entry>
!       <entry><type>bool</type></entry>
        <entry>Role can create databases</entry>
       </row>
  
       <row>
!       <entry><structfield>rolcatupdate</structfield></entry>
!       <entry><type>bool</type></entry>
        <entry>
!        Role can update system catalogs directly.  (Even a superuser cannot do
!        this unless this column is true)
        </entry>
       </row>
  
       <row>
!       <entry><structfield>rolcanlogin</structfield></entry>
!       <entry><type>bool</type></entry>
        <entry>
!        Role can log in. That is, this role can be given as the initial
!        session authorization identifier
        </entry>
       </row>
  
       <row>
!       <entry><structfield>rolreplication</structfield></entry>
!       <entry><type>bool</type></entry>
        <entry>
         Role is a replication role. That is, this role can initiate streaming
         replication (see <xref linkend="streaming-replication">) and set/unset
--- 1391,1493 ----
       </row>
  
       <row>
!       <entry><structfield>rolattr</structfield></entry>
!       <entry><type>bigint</type></entry>
!       <entry>
!        Role attributes; see <xref linkend="sql-createrole"> for details
!       </entry>
!      </row>
! 
!      <row>
!       <entry><structfield>rolconnlimit</structfield></entry>
!       <entry><type>int4</type></entry>
!       <entry>
!        For roles that can log in, this sets maximum number of concurrent
!        connections this role can make.  -1 means no limit.
!       </entry>
!      </row>
! 
!      <row>
!       <entry><structfield>rolpassword</structfield></entry>
!       <entry><type>text</type></entry>
!       <entry>
!        Password (possibly encrypted); null if none.  If the password
!        is encrypted, this column will begin with the string <literal>md5</>
!        followed by a 32-character hexadecimal MD5 hash.  The MD5 hash
!        will be of the user's password concatenated to their user name.
!        For example, if user <literal>joe</> has password <literal>xyzzy</>,
!        <productname>PostgreSQL</> will store the md5 hash of
!        <literal>xyzzyjoe</>.  A password that does not follow that
!        format is assumed to be unencrypted.
!       </entry>
!      </row>
! 
!      <row>
!       <entry><structfield>rolvaliduntil</structfield></entry>
!       <entry><type>timestamptz</type></entry>
!       <entry>Password expiry time (only used for password authentication);
!        null if no expiration</entry>
!      </row>
!     </tbody>
!    </tgroup>
!   </table>
! 
!   <table id="catalog-rolattr-bitmap-table">
!    <title><structfield>rolattr</> bitmap positions</title>
! 
!    <tgroup cols="3">
!     <thead>
!      <row>
!       <entry>Position</entry>
!       <entry>Attribute</entry>
!       <entry>Description</entry>
!      </row>
!     </thead>
! 
!     <tbody>
!      <row>
!       <entry><literal>0</literal></entry>
!       <entry>Superuser</entry>
        <entry>Role has superuser privileges</entry>
       </row>
  
       <row>
!       <entry><literal>1</literal></entry>
!       <entry>Inherit</entry>
!       <entry>Role automatically inherits privileges of roles it is a member of</entry>
       </row>
  
       <row>
!       <entry><literal>2</literal></entry>
!       <entry>Create Role</entry>
        <entry>Role can create more roles</entry>
       </row>
  
       <row>
!       <entry><literal>3</literal></entry>
!       <entry>Create DB</entry>
        <entry>Role can create databases</entry>
       </row>
  
       <row>
!       <entry><literal>4</literal></entry>
!       <entry>Catalog Update</entry>
        <entry>
!        Role can update system catalogs directly.  (Even a superuser cannot do this unless this column is true)
        </entry>
       </row>
  
       <row>
!       <entry><literal>5</literal></entry>
!       <entry>Can Login</entry>
        <entry>
!        Role can log in. That is, this role can be given as the initial session authorization identifier
        </entry>
       </row>
  
       <row>
!       <entry><literal>6</literal></entry>
!       <entry>Replication</entry>
        <entry>
         Role is a replication role. That is, this role can initiate streaming
         replication (see <xref linkend="streaming-replication">) and set/unset
***************
*** 1445,1479 ****
       </row>
  
       <row>
!       <entry><structfield>rolconnlimit</structfield></entry>
!       <entry><type>int4</type></entry>
!       <entry>
!        For roles that can log in, this sets maximum number of concurrent
!        connections this role can make.  -1 means no limit.
!       </entry>
!      </row>
! 
!      <row>
!       <entry><structfield>rolpassword</structfield></entry>
!       <entry><type>text</type></entry>
        <entry>
!        Password (possibly encrypted); null if none.  If the password
!        is encrypted, this column will begin with the string <literal>md5</>
!        followed by a 32-character hexadecimal MD5 hash.  The MD5 hash
!        will be of the user's password concatenated to their user name.
!        For example, if user <literal>joe</> has password <literal>xyzzy</>,
!        <productname>PostgreSQL</> will store the md5 hash of
!        <literal>xyzzyjoe</>.  A password that does not follow that
!        format is assumed to be unencrypted.
        </entry>
       </row>
  
-      <row>
-       <entry><structfield>rolvaliduntil</structfield></entry>
-       <entry><type>timestamptz</type></entry>
-       <entry>Password expiry time (only used for password authentication);
-        null if no expiration</entry>
-      </row>
      </tbody>
     </tgroup>
    </table>
--- 1497,1509 ----
       </row>
  
       <row>
!       <entry><literal>7</literal></entry>
!       <entry>By-pass Row Level Security</entry>
        <entry>
!        Role can by-pass row level security policies when <literal>row_security</> is set <literal>off</>
        </entry>
       </row>
  
      </tbody>
     </tgroup>
    </table>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
new file mode 100644
index ef69b94..74bc702
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT has_function_privilege('joeuser',
*** 15137,15142 ****
--- 15137,15259 ----
      are immediately available without doing <command>SET ROLE</>.
     </para>
  
+    <para>
+     <xref linkend="functions-info-role-attribute-table"> lists functions that
+     allow the user to query role attribute information programmatically.
+    </para>
+ 
+    <table id="functions-info-role-attribute-table">
+     <title>Role Attribute Inquiry Functions</title>
+     <tgroup cols="3">
+      <thead>
+       <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry></row>
+      </thead>
+      <tbody>
+       <row>
+        <entry><literal><function>pg_has_role_attribute(role, attribute)</function></literal></entry>
+        <entry><type>boolean</type></entry>
+        <entry>does role have the permissions allowed by named attribute</entry>
+       </row>
+       <row>
+        <entry><literal><function>pg_check_role_attribute(role, attribute)</function></literal></entry>
+        <entry><type>boolean</type></entry>
+        <entry>does role have the named attribute</entry>
+       </row>
+       <row>
+        <entry><literal><function>pg_check_role_attribute(role_attributes, attribute)</function></literal></entry>
+        <entry><type>boolean</type></entry>
+        <entry>is attribute set in bitmap of role attributes</entry>
+       </row>
+       <row>
+        <entry><literal><function>pg_all_role_attributes(role_attributes)</function></literal></entry>
+        <entry><type>boolean</type></entry>
+        <entry>convert bitmap of role attribute representation to string array</entry>
+       </row>
+      </tbody>
+     </tgroup>
+    </table>
+ 
+    <para>
+     <function>pg_has_role_attribute</function> checks the attribute permissions
+     given to a role.  It will always return <literal>true</literal> for roles
+     with superuser privileges unless the attribute being checked is
+     <literal>CATUPDATE</literal> (superuser cannot bypass
+     <literal>CATUPDATE</literal> permissions). The role can be specified by name
+     and by OID. The attribute is specified by a text string which must evaluate
+     to one of the following role attributes:
+     <literal>SUPERUSER</literal>,
+     <literal>INHERIT</literal>,
+     <literal>CREATEROLE</literal>,
+     <literal>CREATEDB</literal>,
+     <literal>CATUPDATE</literal>,
+     <literal>CANLOGIN</literal>,
+     <literal>REPLICATION</literal>, or
+     <literal>BYPASSRLS</literal>.
+     Example:
+ <programlisting>
+ SELECT pg_has_role_attribute('joe', 'SUPERUSER');
+  pg_has_role_attribute 
+ -----------------------
+  f
+ (1 row)
+ 
+ SELECT rolname, pg_has_role_attribute(oid, 'INHERIT') AS rolinherit FROM pg_roles;
+  rolname  | rolinherit 
+ ----------+------------
+  postgres | t
+  joe      | t
+ (2 rows)
+ </programlisting>
+    </para>
+ 
+    <para>
+     <function>pg_check_role_attribute</function> check the attribute value given
+     to a role.  The role can be specified by name and by OID.  The attribute is
+     specified by a text string which must evaluate to a valid role attribute (see
+     <function>pg_has_role_attribute</function>).  A third variant of this function
+     allows for a bitmap representation (<literal>bigint</literal>) of attributes
+     to be given instead of a role.
+     Example:
+ <programlisting>
+ SELECT pg_check_role_attribute('joe', 'SUPERUSER');
+  pg_check_role_attribute 
+ -------------------------
+  f
+ (1 row)
+ 
+ SELECT rolname, pg_check_role_attribute(oid, 'INHERIT') as rolinherit FROM pg_roles;
+  rolname  | rolinherit 
+ ----------+------------
+  postgres | t
+  joe      | t
+ (2 rows)
+  t
+ (1 row)
+ 
+ 
+ SELECT rolname, pg_check_role_attribute(rolattr, 'SUPERUSER') AS rolsuper FROM pg_authid;
+  rolname  | rolsuper 
+ ----------+----------
+  postgres | t
+  joe      | f
+ (2 rows)
+ </programlisting>
+    </para>
+ 
+    <para>
+     <function>pg_all_role_attributes</function> convert a set of role attributes
+     represented by an <literal>bigint</literal> bitmap to a string array.
+     Example:
+ <programlisting>
+ SELECT rolname, pg_all_role_attributes(rolattr) AS attributes FROM pg_authid;
+  rolname  |                                          attributes                                           
+ ----------+-----------------------------------------------------------------------------------------------
+  postgres | {Superuser,Inherit,"Create Role","Create DB","Catalog Update",Login,Replication,"Bypass RLS"}
+  joe      | {Inherit,Login}
+ (2 rows)
+ </programlisting>
+    </para>
+ 
    <para>
     <xref linkend="functions-info-schema-table"> shows functions that
     determine whether a certain object is <firstterm>visible</> in the
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
new file mode 100644
index 133143d..8475985
*** a/src/backend/access/transam/xlogfuncs.c
--- b/src/backend/access/transam/xlogfuncs.c
***************
*** 22,32 ****
--- 22,34 ----
  #include "access/xlog_internal.h"
  #include "access/xlogutils.h"
  #include "catalog/catalog.h"
+ #include "catalog/pg_authid.h"
  #include "catalog/pg_type.h"
  #include "funcapi.h"
  #include "miscadmin.h"
  #include "replication/walreceiver.h"
  #include "storage/smgr.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/numeric.h"
  #include "utils/guc.h"
*************** pg_start_backup(PG_FUNCTION_ARGS)
*** 54,60 ****
  
  	backupidstr = text_to_cstring(backupid);
  
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		   errmsg("must be superuser or replication role to run a backup")));
--- 56,62 ----
  
  	backupidstr = text_to_cstring(backupid);
  
! 	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		   errmsg("must be superuser or replication role to run a backup")));
*************** pg_stop_backup(PG_FUNCTION_ARGS)
*** 82,88 ****
  {
  	XLogRecPtr	stoppoint;
  
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		 (errmsg("must be superuser or replication role to run a backup"))));
--- 84,90 ----
  {
  	XLogRecPtr	stoppoint;
  
! 	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		 (errmsg("must be superuser or replication role to run a backup"))));
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
new file mode 100644
index d30612c..4663429
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
*************** aclcheck_error_type(AclResult aclerr, Oi
*** 3423,3448 ****
  }
  
  
- /* Check if given user has rolcatupdate privilege according to pg_authid */
- static bool
- has_rolcatupdate(Oid roleid)
- {
- 	bool		rolcatupdate;
- 	HeapTuple	tuple;
- 
- 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (!HeapTupleIsValid(tuple))
- 		ereport(ERROR,
- 				(errcode(ERRCODE_UNDEFINED_OBJECT),
- 				 errmsg("role with OID %u does not exist", roleid)));
- 
- 	rolcatupdate = ((Form_pg_authid) GETSTRUCT(tuple))->rolcatupdate;
- 
- 	ReleaseSysCache(tuple);
- 
- 	return rolcatupdate;
- }
- 
  /*
   * Relay for the various pg_*_mask routines depending on object kind
   */
--- 3423,3428 ----
*************** pg_class_aclmask(Oid table_oid, Oid role
*** 3630,3636 ****
  	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
  		IsSystemClass(table_oid, classForm) &&
  		classForm->relkind != RELKIND_VIEW &&
! 		!has_rolcatupdate(roleid) &&
  		!allowSystemTableMods)
  	{
  #ifdef ACLDEBUG
--- 3610,3616 ----
  	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
  		IsSystemClass(table_oid, classForm) &&
  		classForm->relkind != RELKIND_VIEW &&
! 		!has_role_attribute(roleid, ROLE_ATTR_CATUPDATE) &&
  		!allowSystemTableMods)
  	{
  #ifdef ACLDEBUG
*************** pg_extension_ownercheck(Oid ext_oid, Oid
*** 5051,5102 ****
  }
  
  /*
!  * Check whether specified role has CREATEROLE privilege (or is a superuser)
   *
   * Note: roles do not have owners per se; instead we use this test in
   * places where an ownership-like permissions test is needed for a role.
   * Be sure to apply it to the role trying to do the operation, not the
!  * role being operated on!	Also note that this generally should not be
   * considered enough privilege if the target role is a superuser.
   * (We don't handle that consideration here because we want to give a
   * separate error message for such cases, so the caller has to deal with it.)
   */
  bool
! has_createrole_privilege(Oid roleid)
  {
! 	bool		result = false;
! 	HeapTuple	utup;
! 
! 	/* Superusers bypass all permission checking. */
! 	if (superuser_arg(roleid))
  		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  bool
! has_bypassrls_privilege(Oid roleid)
  {
! 	bool		result = false;
! 	HeapTuple	utup;
  
! 	/* Superusers bypass all permission checking. */
! 	if (superuser_arg(roleid))
! 		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolbypassrls;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  /*
--- 5031,5110 ----
  }
  
  /*
!  * has_role_attribute
!  *   Check if the role with the specified id has been assigned a specific role
!  *   attribute.
!  *
!  * roleid - the oid of the role to check.
!  * attribute - the attribute to check.
!  *
!  * Note: Use this function for role attribute permission checking as it accounts
!  * for superuser status.  It will always return true for roles with superuser
!  * privileges unless the attribute being checked is CATUPDATE (superusers are not
!  * allowed to bypass CATUPDATE permissions).
   *
   * Note: roles do not have owners per se; instead we use this test in
   * places where an ownership-like permissions test is needed for a role.
   * Be sure to apply it to the role trying to do the operation, not the
!  * role being operated on!  Also note that this generally should not be
   * considered enough privilege if the target role is a superuser.
   * (We don't handle that consideration here because we want to give a
   * separate error message for such cases, so the caller has to deal with it.)
   */
  bool
! has_role_attribute(Oid roleid, RoleAttr attribute)
  {
! 	/*
! 	 * Superusers bypass all permission checking except in the case of CATUPDATE.
! 	 */
! 	if (!(attribute & ROLE_ATTR_CATUPDATE) && superuser_arg(roleid))
  		return true;
  
! 	return check_role_attribute(roleid, attribute);
  }
  
+ /*
+  * have_role_attribute
+  *   Convenience function for checking if the role id returned by GetUserId() has
+  *   been assigned a specific role attribute.
+  *
+  * attribute - the attribute to check.
+  */
  bool
! have_role_attribute(RoleAttr attribute)
  {
! 	return has_role_attribute(GetUserId(), attribute);
! }
  
! /*
!  * check_role_attribute
!  *   Check if the role with the specified id has been assigned a specific role
!  *   attribute.
!  *
!  * roleid - the oid of the role to check.
!  * attribute - the attribute to check.
!  *
!  * Note: This function should only be used for checking the value of an individual
!  * attribute in the rolattr bitmap and should *not* be used for permission checking.
!  * For the purposes of permission checking use 'has_role_attribute' instead.
!  */
! bool
! check_role_attribute(Oid roleid, RoleAttr attribute)
! {
! 	RoleAttr	attributes;
! 	HeapTuple	tuple;
  
! 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 
! 	if (!HeapTupleIsValid(tuple))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("role with OID %u does not exist", roleid)));
! 
! 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 	ReleaseSysCache(tuple);
! 
! 	return (attributes & attribute);
  }
  
  /*
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
new file mode 100644
index ca89879..2929b66
*** a/src/backend/catalog/genbki.pl
--- b/src/backend/catalog/genbki.pl
*************** my $BOOTSTRAP_SUPERUSERID =
*** 90,95 ****
--- 90,97 ----
    find_defined_symbol('pg_authid.h', 'BOOTSTRAP_SUPERUSERID');
  my $PG_CATALOG_NAMESPACE =
    find_defined_symbol('pg_namespace.h', 'PG_CATALOG_NAMESPACE');
+ my $ROLE_ATTR_ALL =
+   find_defined_symbol('pg_authid.h', 'ROLE_ATTR_ALL');
  
  # Read all the input header files into internal data structures
  my $catalogs = Catalog::Catalogs(@input_files);
*************** foreach my $catname (@{ $catalogs->{name
*** 144,149 ****
--- 146,152 ----
  			# substitute constant values we acquired above
  			$row->{bki_values} =~ s/\bPGUID\b/$BOOTSTRAP_SUPERUSERID/g;
  			$row->{bki_values} =~ s/\bPGNSP\b/$PG_CATALOG_NAMESPACE/g;
+ 			$row->{bki_values} =~ s/\bPGROLATTRALL/$ROLE_ATTR_ALL/g;
  
  			# Save pg_type info for pg_attribute processing below
  			if ($catname eq 'pg_type')
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
new file mode 100644
index a036c62..87b6d8c
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
*************** CREATE VIEW user_mapping_options AS
*** 2884,2890 ****
             CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
             CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                         OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
!                        OR (SELECT rolsuper FROM pg_authid WHERE rolname = current_user) THEN (pg_options_to_table(um.umoptions)).option_value
                       ELSE NULL END AS character_data) AS option_value
      FROM _pg_user_mappings um;
  
--- 2884,2895 ----
             CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
             CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                         OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
!                        OR (
!                             SELECT pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER') AS rolsuper
!                             FROM pg_authid
!                             WHERE rolname = current_user
!                           )
!                        THEN (pg_options_to_table(um.umoptions)).option_value
                       ELSE NULL END AS character_data) AS option_value
      FROM _pg_user_mappings um;
  
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
new file mode 100644
index e261307..6df7e4d
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
*************** check_object_ownership(Oid roleid, Objec
*** 1310,1316 ****
  			}
  			else
  			{
! 				if (!has_createrole_privilege(roleid))
  					ereport(ERROR,
  							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  							 errmsg("must have CREATEROLE privilege")));
--- 1310,1316 ----
  			}
  			else
  			{
! 				if (!has_role_attribute(roleid, ROLE_ATTR_CREATEROLE))
  					ereport(ERROR,
  							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  							 errmsg("must have CREATEROLE privilege")));
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
new file mode 100644
index 22b8cee..ae93832
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 9,25 ****
  CREATE VIEW pg_roles AS
      SELECT
          rolname,
!         rolsuper,
!         rolinherit,
!         rolcreaterole,
!         rolcreatedb,
!         rolcatupdate,
!         rolcanlogin,
!         rolreplication,
          rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
-         rolbypassrls,
          setconfig as rolconfig,
          pg_authid.oid
      FROM pg_authid LEFT JOIN pg_db_role_setting s
--- 9,25 ----
  CREATE VIEW pg_roles AS
      SELECT
          rolname,
!         pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER') AS rolsuper,
!         pg_check_role_attribute(pg_authid.rolattr, 'INHERIT') AS rolinherit,
!         pg_check_role_attribute(pg_authid.rolattr, 'CREATEROLE') AS rolcreaterole,
!         pg_check_role_attribute(pg_authid.rolattr, 'CREATEDB') AS rolcreatedb,
!         pg_check_role_attribute(pg_authid.rolattr, 'CATUPDATE') AS rolcatupdate,
!         pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN') AS rolcanlogin,
!         pg_check_role_attribute(pg_authid.rolattr, 'REPLICATION') AS rolreplication,
!         pg_check_role_attribute(pg_authid.rolattr, 'BYPASSRLS') AS rolbypassrls,
          rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
          setconfig as rolconfig,
          pg_authid.oid
      FROM pg_authid LEFT JOIN pg_db_role_setting s
*************** CREATE VIEW pg_shadow AS
*** 29,44 ****
      SELECT
          rolname AS usename,
          pg_authid.oid AS usesysid,
!         rolcreatedb AS usecreatedb,
!         rolsuper AS usesuper,
!         rolcatupdate AS usecatupd,
!         rolreplication AS userepl,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          setconfig AS useconfig
      FROM pg_authid LEFT JOIN pg_db_role_setting s
      ON (pg_authid.oid = setrole AND setdatabase = 0)
!     WHERE rolcanlogin;
  
  REVOKE ALL on pg_shadow FROM public;
  
--- 29,44 ----
      SELECT
          rolname AS usename,
          pg_authid.oid AS usesysid,
!         pg_check_role_attribute(pg_authid.rolattr, 'CREATEDB') AS usecreatedb,
!         pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER') AS usesuper,
!         pg_check_role_attribute(pg_authid.rolattr, 'CATUPDATE') AS usecatupd,
!         pg_check_role_attribute(pg_authid.rolattr, 'REPLICATION') AS userepl,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          setconfig AS useconfig
      FROM pg_authid LEFT JOIN pg_db_role_setting s
      ON (pg_authid.oid = setrole AND setdatabase = 0)
!     WHERE pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN');
  
  REVOKE ALL on pg_shadow FROM public;
  
*************** CREATE VIEW pg_group AS
*** 48,54 ****
          oid AS grosysid,
          ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
      FROM pg_authid
!     WHERE NOT rolcanlogin;
  
  CREATE VIEW pg_user AS
      SELECT
--- 48,54 ----
          oid AS grosysid,
          ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
      FROM pg_authid
!     WHERE NOT pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN');
  
  CREATE VIEW pg_user AS
      SELECT
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
new file mode 100644
index 1a5244c..c079168
*** a/src/backend/commands/dbcommands.c
--- b/src/backend/commands/dbcommands.c
*************** static bool get_db_info(const char *name
*** 85,91 ****
  			Oid *dbLastSysOidP, TransactionId *dbFrozenXidP,
  			MultiXactId *dbMinMultiP,
  			Oid *dbTablespace, char **dbCollate, char **dbCtype);
- static bool have_createdb_privilege(void);
  static void remove_dbtablespaces(Oid db_id);
  static bool check_db_file_conflict(Oid db_id);
  static int	errdetail_busy_db(int notherbackends, int npreparedxacts);
--- 85,90 ----
*************** createdb(const CreatedbStmt *stmt)
*** 291,297 ****
  	 * "giveaway" attacks.  Note that a superuser will always have both of
  	 * these privileges a fortiori.
  	 */
! 	if (!have_createdb_privilege())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to create database")));
--- 290,296 ----
  	 * "giveaway" attacks.  Note that a superuser will always have both of
  	 * these privileges a fortiori.
  	 */
! 	if (!have_role_attribute(ROLE_ATTR_CREATEDB))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to create database")));
*************** RenameDatabase(const char *oldname, cons
*** 965,971 ****
  					   oldname);
  
  	/* must have createdb rights */
! 	if (!have_createdb_privilege())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to rename database")));
--- 964,970 ----
  					   oldname);
  
  	/* must have createdb rights */
! 	if (!have_role_attribute(ROLE_ATTR_CREATEDB))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to rename database")));
*************** AlterDatabaseOwner(const char *dbname, O
*** 1623,1629 ****
  		 * databases.  Because superusers will always have this right, we need
  		 * no special case for them.
  		 */
! 		if (!have_createdb_privilege())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				   errmsg("permission denied to change owner of database")));
--- 1622,1628 ----
  		 * databases.  Because superusers will always have this right, we need
  		 * no special case for them.
  		 */
! 		if (!have_role_attribute(ROLE_ATTR_CREATEDB))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				   errmsg("permission denied to change owner of database")));
*************** get_db_info(const char *name, LOCKMODE l
*** 1802,1827 ****
  	return result;
  }
  
- /* Check if current user has createdb privileges */
- static bool
- have_createdb_privilege(void)
- {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
- 	/* Superusers can always do everything */
- 	if (superuser())
- 		return true;
- 
- 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId()));
- 	if (HeapTupleIsValid(utup))
- 	{
- 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreatedb;
- 		ReleaseSysCache(utup);
- 	}
- 	return result;
- }
- 
  /*
   * Remove tablespace directories
   *
--- 1801,1806 ----
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
new file mode 100644
index 1a73fd8..5bde610
*** a/src/backend/commands/user.c
--- b/src/backend/commands/user.c
*************** static void DelRoleMems(const char *role
*** 56,69 ****
  			bool admin_opt);
  
  
- /* Check if current user has createrole privileges */
- static bool
- have_createrole_privilege(void)
- {
- 	return has_createrole_privilege(GetUserId());
- }
- 
- 
  /*
   * CREATE ROLE
   */
--- 56,61 ----
*************** CreateRole(CreateRoleStmt *stmt)
*** 88,93 ****
--- 80,86 ----
  	bool		canlogin = false;		/* Can this user login? */
  	bool		isreplication = false;	/* Is this a replication role? */
  	bool		bypassrls = false;		/* Is this a row security enabled role? */
+ 	RoleAttr	attributes = ROLE_ATTR_NONE;	/* role attributes, initialized to none. */
  	int			connlimit = -1; /* maximum connections allowed */
  	List	   *addroleto = NIL;	/* roles to make this a member of */
  	List	   *rolemembers = NIL;		/* roles to be members of this role */
*************** CreateRole(CreateRoleStmt *stmt)
*** 249,254 ****
--- 242,249 ----
  
  	if (dpassword && dpassword->arg)
  		password = strVal(dpassword->arg);
+ 
+ 	/* Role Attributes */
  	if (dissuper)
  		issuper = intVal(dissuper->arg) != 0;
  	if (dinherit)
*************** CreateRole(CreateRoleStmt *stmt)
*** 261,266 ****
--- 256,264 ----
  		canlogin = intVal(dcanlogin->arg) != 0;
  	if (disreplication)
  		isreplication = intVal(disreplication->arg) != 0;
+ 	if (dbypassRLS)
+ 		bypassrls = intVal(dbypassRLS->arg) != 0;
+ 
  	if (dconnlimit)
  	{
  		connlimit = intVal(dconnlimit->arg);
*************** CreateRole(CreateRoleStmt *stmt)
*** 277,284 ****
  		adminmembers = (List *) dadminmembers->arg;
  	if (dvalidUntil)
  		validUntil = strVal(dvalidUntil->arg);
- 	if (dbypassRLS)
- 		bypassrls = intVal(dbypassRLS->arg) != 0;
  
  	/* Check some permissions first */
  	if (issuper)
--- 275,280 ----
*************** CreateRole(CreateRoleStmt *stmt)
*** 304,310 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to create role")));
--- 300,306 ----
  	}
  	else
  	{
! 		if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to create role")));
*************** CreateRole(CreateRoleStmt *stmt)
*** 355,360 ****
--- 351,372 ----
  								validUntil_datum,
  								validUntil_null);
  
+ 	/* Set all role attributes */
+ 	if (issuper)
+ 		attributes |= ROLE_ATTR_SUPERUSER;
+ 	if (inherit)
+ 		attributes |= ROLE_ATTR_INHERIT;
+ 	if (createrole)
+ 		attributes |= ROLE_ATTR_CREATEROLE;
+ 	if (createdb)
+ 		attributes |= ROLE_ATTR_CREATEDB;
+ 	if (canlogin)
+ 		attributes |= ROLE_ATTR_CANLOGIN;
+ 	if (isreplication)
+ 		attributes |= ROLE_ATTR_REPLICATION;
+ 	if (bypassrls)
+ 		attributes |= ROLE_ATTR_BYPASSRLS;
+ 
  	/*
  	 * Build a tuple to insert
  	 */
*************** CreateRole(CreateRoleStmt *stmt)
*** 364,377 ****
  	new_record[Anum_pg_authid_rolname - 1] =
  		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
  
! 	new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
! 	new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
! 	new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
! 	new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
! 	/* superuser gets catupdate right by default */
! 	new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper);
! 	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
! 	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
  	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
  
  	if (password)
--- 376,383 ----
  	new_record[Anum_pg_authid_rolname - 1] =
  		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
  
! 	new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
! 
  	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
  
  	if (password)
*************** CreateRole(CreateRoleStmt *stmt)
*** 394,401 ****
  	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
  	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
  
- 	new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls);
- 
  	tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
  
  	/*
--- 400,405 ----
*************** AlterRole(AlterRoleStmt *stmt)
*** 508,513 ****
--- 512,518 ----
  	DefElem    *dvalidUntil = NULL;
  	DefElem    *dbypassRLS = NULL;
  	Oid			roleid;
+ 	RoleAttr	attributes;
  
  	/* Extract options from the statement node tree */
  	foreach(option, stmt->options)
*************** AlterRole(AlterRoleStmt *stmt)
*** 661,688 ****
  	 * To mess with a superuser you gotta be superuser; else you need
  	 * createrole, or just want to change your own password
  	 */
! 	if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper || issuper >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter superusers")));
  	}
! 	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolreplication || isreplication >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter replication users")));
  	}
! 	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolbypassrls || bypassrls >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to change bypassrls attribute")));
  	}
! 	else if (!have_createrole_privilege())
  	{
  		if (!(inherit < 0 &&
  			  createrole < 0 &&
--- 666,696 ----
  	 * To mess with a superuser you gotta be superuser; else you need
  	 * createrole, or just want to change your own password
  	 */
! 
! 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 
! 	if ((attributes & ROLE_ATTR_SUPERUSER) || issuper >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter superusers")));
  	}
! 	else if ((attributes & ROLE_ATTR_REPLICATION) || isreplication >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter replication users")));
  	}
! 	else if ((attributes & ROLE_ATTR_BYPASSRLS) || bypassrls >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to change bypassrls attribute")));
  	}
! 	else if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
  	{
  		if (!(inherit < 0 &&
  			  createrole < 0 &&
*************** AlterRole(AlterRoleStmt *stmt)
*** 743,785 ****
  	 */
  	if (issuper >= 0)
  	{
! 		new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);
! 		new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
! 
! 		new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);
! 		new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true;
  	}
  
  	if (inherit >= 0)
  	{
! 		new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
! 		new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
  	}
  
  	if (createrole >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);
! 		new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
  	}
  
  	if (createdb >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0);
! 		new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true;
  	}
  
  	if (canlogin >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0);
! 		new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true;
  	}
  
  	if (isreplication >= 0)
  	{
! 		new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication > 0);
! 		new_record_repl[Anum_pg_authid_rolreplication - 1] = true;
  	}
  
  	if (dconnlimit)
  	{
  		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
--- 751,807 ----
  	 */
  	if (issuper >= 0)
  	{
! 		attributes = (issuper > 0) ? attributes | (ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE)
! 								   : attributes & ~(ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (inherit >= 0)
  	{
! 		attributes = (inherit > 0) ? attributes | ROLE_ATTR_INHERIT
! 								   : attributes & ~(ROLE_ATTR_INHERIT);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (createrole >= 0)
  	{
! 		attributes = (createrole > 0) ? attributes | ROLE_ATTR_CREATEROLE
! 									  : attributes & ~(ROLE_ATTR_CREATEROLE);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (createdb >= 0)
  	{
! 		attributes = (createdb > 0) ? attributes | ROLE_ATTR_CREATEDB
! 									: attributes & ~(ROLE_ATTR_CREATEDB);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (canlogin >= 0)
  	{
! 		attributes = (canlogin > 0) ? attributes | ROLE_ATTR_CANLOGIN
! 									: attributes & ~(ROLE_ATTR_CANLOGIN);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (isreplication >= 0)
  	{
! 		attributes = (isreplication > 0) ? attributes | ROLE_ATTR_REPLICATION
! 										 : attributes & ~(ROLE_ATTR_REPLICATION);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
! 	}
! 
! 	if (bypassrls >= 0)
! 	{
! 		attributes = (bypassrls > 0) ? attributes | ROLE_ATTR_BYPASSRLS
! 										 : attributes & ~(ROLE_ATTR_BYPASSRLS);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
+ 	/* If any role attributes were set, then update. */
+ 	if (new_record_repl[Anum_pg_authid_rolattr - 1])
+ 		new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
+ 
  	if (dconnlimit)
  	{
  		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
*************** AlterRole(AlterRoleStmt *stmt)
*** 815,825 ****
  	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
  	new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
  
- 	if (bypassrls >= 0)
- 	{
- 		new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls > 0);
- 		new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
- 	}
  
  	new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
  								  new_record_nulls, new_record_repl);
--- 837,842 ----
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 867,872 ****
--- 884,890 ----
  	HeapTuple	roletuple;
  	Oid			databaseid = InvalidOid;
  	Oid			roleid = InvalidOid;
+ 	RoleAttr	attributes;
  
  	if (stmt->role)
  	{
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 889,895 ****
  		 * To mess with a superuser you gotta be superuser; else you need
  		 * createrole, or just want to change your own settings
  		 */
! 		if (((Form_pg_authid) GETSTRUCT(roletuple))->rolsuper)
  		{
  			if (!superuser())
  				ereport(ERROR,
--- 907,914 ----
  		 * To mess with a superuser you gotta be superuser; else you need
  		 * createrole, or just want to change your own settings
  		 */
! 		attributes = ((Form_pg_authid) GETSTRUCT(roletuple))->rolattr;
! 		if (attributes & ROLE_ATTR_SUPERUSER)
  		{
  			if (!superuser())
  				ereport(ERROR,
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 898,904 ****
  		}
  		else
  		{
! 			if (!have_createrole_privilege() &&
  				HeapTupleGetOid(roletuple) != GetUserId())
  				ereport(ERROR,
  						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 917,923 ----
  		}
  		else
  		{
! 			if (!have_role_attribute(ROLE_ATTR_CREATEROLE) &&
  				HeapTupleGetOid(roletuple) != GetUserId())
  				ereport(ERROR,
  						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
*************** DropRole(DropRoleStmt *stmt)
*** 951,957 ****
  				pg_auth_members_rel;
  	ListCell   *item;
  
! 	if (!have_createrole_privilege())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to drop role")));
--- 970,976 ----
  				pg_auth_members_rel;
  	ListCell   *item;
  
! 	if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to drop role")));
*************** DropRole(DropRoleStmt *stmt)
*** 973,978 ****
--- 992,998 ----
  		char	   *detail_log;
  		SysScanDesc sscan;
  		Oid			roleid;
+ 		RoleAttr	attributes;
  
  		tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
  		if (!HeapTupleIsValid(tuple))
*************** DropRole(DropRoleStmt *stmt)
*** 1013,1020 ****
  		 * roles but not superuser roles.  This is mainly to avoid the
  		 * scenario where you accidentally drop the last superuser.
  		 */
! 		if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper &&
! 			!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to drop superusers")));
--- 1033,1040 ----
  		 * roles but not superuser roles.  This is mainly to avoid the
  		 * scenario where you accidentally drop the last superuser.
  		 */
! 		attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 		if ((attributes & ROLE_ATTR_SUPERUSER) && !superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to drop superusers")));
*************** RenameRole(const char *oldname, const ch
*** 1128,1133 ****
--- 1148,1154 ----
  	bool		repl_repl[Natts_pg_authid];
  	int			i;
  	Oid			roleid;
+ 	RoleAttr	attributes;
  
  	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
  	dsc = RelationGetDescr(rel);
*************** RenameRole(const char *oldname, const ch
*** 1173,1179 ****
  	/*
  	 * createrole is enough privilege unless you want to mess with a superuser
  	 */
! 	if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
  	{
  		if (!superuser())
  			ereport(ERROR,
--- 1194,1201 ----
  	/*
  	 * createrole is enough privilege unless you want to mess with a superuser
  	 */
! 	attributes = ((Form_pg_authid) GETSTRUCT(oldtuple))->rolattr;
! 	if (attributes & ROLE_ATTR_SUPERUSER)
  	{
  		if (!superuser())
  			ereport(ERROR,
*************** RenameRole(const char *oldname, const ch
*** 1182,1188 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to rename role")));
--- 1204,1210 ----
  	}
  	else
  	{
! 		if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to rename role")));
*************** AddRoleMems(const char *rolename, Oid ro
*** 1409,1415 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege() &&
  			!is_admin_of_role(grantorId, roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 1431,1437 ----
  	}
  	else
  	{
! 		if (!have_role_attribute(ROLE_ATTR_CREATEROLE) &&
  			!is_admin_of_role(grantorId, roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
*************** DelRoleMems(const char *rolename, Oid ro
*** 1555,1561 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege() &&
  			!is_admin_of_role(GetUserId(), roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 1577,1583 ----
  	}
  	else
  	{
! 		if (!have_role_attribute(ROLE_ATTR_CREATEROLE) &&
  			!is_admin_of_role(GetUserId(), roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
new file mode 100644
index 6ce8dae..491dc38
*** a/src/backend/commands/variable.c
--- b/src/backend/commands/variable.c
*************** check_session_authorization(char **newva
*** 776,781 ****
--- 776,782 ----
  	Oid			roleid;
  	bool		is_superuser;
  	role_auth_extra *myextra;
+ 	RoleAttr	attributes;
  
  	/* Do nothing for the boot_val default of NULL */
  	if (*newval == NULL)
*************** check_session_authorization(char **newva
*** 800,806 ****
  	}
  
  	roleid = HeapTupleGetOid(roleTup);
! 	is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
  
  	ReleaseSysCache(roleTup);
  
--- 801,808 ----
  	}
  
  	roleid = HeapTupleGetOid(roleTup);
! 	attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
! 	is_superuser = (attributes & ROLE_ATTR_SUPERUSER);
  
  	ReleaseSysCache(roleTup);
  
*************** check_role(char **newval, void **extra,
*** 844,849 ****
--- 846,852 ----
  	Oid			roleid;
  	bool		is_superuser;
  	role_auth_extra *myextra;
+ 	RoleAttr	attributes;
  
  	if (strcmp(*newval, "none") == 0)
  	{
*************** check_role(char **newval, void **extra,
*** 872,878 ****
  		}
  
  		roleid = HeapTupleGetOid(roleTup);
! 		is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
  
  		ReleaseSysCache(roleTup);
  
--- 875,882 ----
  		}
  
  		roleid = HeapTupleGetOid(roleTup);
! 		attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
! 		is_superuser = (attributes & ROLE_ATTR_SUPERUSER);
  
  		ReleaseSysCache(roleTup);
  
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
new file mode 100644
index 1977f09..5f1126e
*** a/src/backend/replication/logical/logicalfuncs.c
--- b/src/backend/replication/logical/logicalfuncs.c
***************
*** 23,34 ****
--- 23,36 ----
  
  #include "access/xlog_internal.h"
  
+ #include "catalog/pg_authid.h"
  #include "catalog/pg_type.h"
  
  #include "nodes/makefuncs.h"
  
  #include "mb/pg_wchar.h"
  
+ #include "utils/acl.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/inval.h"
*************** XLogRead(char *buf, TimeLineID tli, XLog
*** 205,211 ****
  static void
  check_permissions(void)
  {
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
--- 207,213 ----
  static void
  check_permissions(void)
  {
! 	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
new file mode 100644
index bd4701f..bc6a23a
*** a/src/backend/replication/slotfuncs.c
--- b/src/backend/replication/slotfuncs.c
***************
*** 17,32 ****
  #include "miscadmin.h"
  
  #include "access/htup_details.h"
  #include "replication/slot.h"
  #include "replication/logical.h"
  #include "replication/logicalfuncs.h"
  #include "utils/builtins.h"
  #include "utils/pg_lsn.h"
  
  static void
  check_permissions(void)
  {
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
--- 17,34 ----
  #include "miscadmin.h"
  
  #include "access/htup_details.h"
+ #include "catalog/pg_authid.h"
  #include "replication/slot.h"
  #include "replication/logical.h"
  #include "replication/logicalfuncs.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/pg_lsn.h"
  
  static void
  check_permissions(void)
  {
! 	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
new file mode 100644
index 6c232dc..58633cc
*** a/src/backend/rewrite/rowsecurity.c
--- b/src/backend/rewrite/rowsecurity.c
***************
*** 36,41 ****
--- 36,42 ----
  #include "access/heapam.h"
  #include "access/htup_details.h"
  #include "access/sysattr.h"
+ #include "catalog/pg_authid.h"
  #include "catalog/pg_class.h"
  #include "catalog/pg_inherits_fn.h"
  #include "catalog/pg_policy.h"
*************** check_enable_rls(Oid relid, Oid checkAsU
*** 521,527 ****
  	 */
  	if (!checkAsUser && row_security == ROW_SECURITY_OFF)
  	{
! 		if (has_bypassrls_privilege(user_id))
  			/* OK to bypass */
  			return RLS_NONE_ENV;
  		else
--- 522,528 ----
  	 */
  	if (!checkAsUser && row_security == ROW_SECURITY_OFF)
  	{
! 		if (has_role_attribute(user_id, ROLE_ATTR_BYPASSRLS))
  			/* OK to bypass */
  			return RLS_NONE_ENV;
  		else
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
new file mode 100644
index dc6eb2c..93017b2
*** a/src/backend/utils/adt/acl.c
--- b/src/backend/utils/adt/acl.c
*************** static Oid	convert_type_name(text *typen
*** 115,120 ****
--- 115,121 ----
  static AclMode convert_type_priv_string(text *priv_type_text);
  static AclMode convert_role_priv_string(text *priv_type_text);
  static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
+ static RoleAttr convert_role_attr_string(text *attr_type_text);
  
  static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue);
  
*************** pg_role_aclcheck(Oid role_oid, Oid rolei
*** 4602,4607 ****
--- 4603,4790 ----
  	return ACLCHECK_NO_PRIV;
  }
  
+ /*
+  * pg_has_role_attribute_id
+  *		Check that the role with the given oid has the given named role
+  *		attribute.
+  *
+  * Note: This function applies superuser checks.  Therefore, if the provided
+  * role is a superuser, then the result will always be true.
+  */
+ Datum
+ pg_has_role_attribute_id(PG_FUNCTION_ARGS)
+ {
+ 	Oid			roleoid = PG_GETARG_OID(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(has_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_has_role_attribute_name
+  *		Check that the named role has the given named role attribute.
+  *
+  * Note: This function applies superuser checks.  Therefore, if the provided
+  * role is a superuser, then the result will always be true.
+  */
+ Datum
+ pg_has_role_attribute_name(PG_FUNCTION_ARGS)
+ {
+ 	Name		rolename = PG_GETARG_NAME(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	Oid			roleoid;
+ 	RoleAttr	attribute;
+ 
+ 	roleoid = get_role_oid(NameStr(*rolename), false);
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(has_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_check_role_attribute_id
+  *		Check that the role with the given oid has the given named role
+  *		attribute.
+  *
+  * Note: This function is different from 'pg_has_role_attribute_id_attr' in that
+  * it does *not* apply any superuser checks.  Therefore, this function will
+  * always return the set value of the attribute, despite the superuser-ness of
+  * the provided role.
+  */
+ Datum
+ pg_check_role_attribute_id(PG_FUNCTION_ARGS)
+ {
+ 	Oid			roleoid = PG_GETARG_OID(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(check_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_check_role_attribute_name
+  *		Check that the named role has the given named role attribute.
+  *
+  * Note: This function is different from 'pg_has_role_attribute_name_attr' in
+  * that it does *not* apply any superuser checks.  Therefore, this function will
+  * always return the set value of the attribute, despite the superuser-ness of
+  * the provided role.
+  */
+ Datum
+ pg_check_role_attribute_name(PG_FUNCTION_ARGS)
+ {
+ 	Name		rolename = PG_GETARG_NAME(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	Oid			roleoid;
+ 	RoleAttr	attribute;
+ 
+ 	roleoid = get_role_oid(NameStr(*rolename), false);
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(check_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_check_role_attribute_attrs
+  *		Check that the named attribute is enabled in the given RoleAttr
+  *		representation of role attributes.
+  */
+ Datum
+ pg_check_role_attribute_attrs(PG_FUNCTION_ARGS)
+ {
+ 	RoleAttr	attributes = PG_GETARG_INT64(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(attributes & attribute);
+ }
+ 
+ /*
+  * pg_all_role_attributes
+  *		Convert a RoleAttr representation of role attributes into an array of
+  *		corresponding text values.
+  *
+  * The first and only argument is a RoleAttr (int64) representation of the
+  * role attributes.
+  */
+ Datum
+ pg_all_role_attributes(PG_FUNCTION_ARGS)
+ {
+ 	RoleAttr		attributes = PG_GETARG_INT64(0);
+ 	Datum		   *temp_array;
+ 	ArrayType	   *result;
+ 	int				i = 0;
+ 
+ 	/*
+ 	 * If no attributes are assigned, then there is no need to go through the
+ 	 * individual checks for which are assigned.  Therefore, we can short circuit
+ 	 * and return an empty array.
+ 	 */
+ 	if (attributes == ROLE_ATTR_NONE)
+ 		PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));
+ 
+ 	temp_array = (Datum *) palloc(N_ROLE_ATTRIBUTES * sizeof(Datum));
+ 
+ 	/* Determine which attributes are assigned. */
+ 	if (attributes & ROLE_ATTR_SUPERUSER)
+ 		temp_array[i++] = CStringGetTextDatum("Superuser");
+ 	if (attributes & ROLE_ATTR_INHERIT)
+ 		temp_array[i++] = CStringGetTextDatum("Inherit");
+ 	if (attributes & ROLE_ATTR_CREATEROLE)
+ 		temp_array[i++] = CStringGetTextDatum("Create Role");
+ 	if (attributes & ROLE_ATTR_CREATEDB)
+ 		temp_array[i++] = CStringGetTextDatum("Create DB");
+ 	if (attributes & ROLE_ATTR_CATUPDATE)
+ 		temp_array[i++] = CStringGetTextDatum("Catalog Update");
+ 	if (attributes & ROLE_ATTR_CANLOGIN)
+ 		temp_array[i++] = CStringGetTextDatum("Login");
+ 	if (attributes & ROLE_ATTR_REPLICATION)
+ 		temp_array[i++] = CStringGetTextDatum("Replication");
+ 	if (attributes & ROLE_ATTR_BYPASSRLS)
+ 		temp_array[i++] = CStringGetTextDatum("Bypass RLS");
+ 
+ 	result = construct_array(temp_array, i, TEXTOID, -1, false, 'i');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * convert_role_attr_string
+  *		Convert text string to RoleAttr value.
+  */
+ static RoleAttr
+ convert_role_attr_string(text *attr_type_text)
+ {
+ 	char	   *attr_type = text_to_cstring(attr_type_text);
+ 
+ 	if (pg_strcasecmp(attr_type, "SUPERUSER") == 0)
+ 		return ROLE_ATTR_SUPERUSER;
+ 	else if (pg_strcasecmp(attr_type, "INHERIT") == 0)
+ 		return ROLE_ATTR_INHERIT;
+ 	else if (pg_strcasecmp(attr_type, "CREATEROLE") == 0)
+ 		return ROLE_ATTR_CREATEROLE;
+ 	else if (pg_strcasecmp(attr_type, "CREATEDB") == 0)
+ 		return ROLE_ATTR_CREATEDB;
+ 	else if (pg_strcasecmp(attr_type, "CATUPDATE") == 0)
+ 		return ROLE_ATTR_CATUPDATE;
+ 	else if (pg_strcasecmp(attr_type, "CANLOGIN") == 0)
+ 		return ROLE_ATTR_CANLOGIN;
+ 	else if (pg_strcasecmp(attr_type, "REPLICATION") == 0)
+ 		return ROLE_ATTR_REPLICATION;
+ 	else if (pg_strcasecmp(attr_type, "BYPASSRLS") == 0)
+ 		return ROLE_ATTR_BYPASSRLS;
+ 	else
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("unrecognized role attribute: \"%s\"", attr_type)));
+ }
  
  /*
   * initialization function (called by InitPostgres)
*************** RoleMembershipCacheCallback(Datum arg, i
*** 4634,4656 ****
  }
  
  
- /* Check if specified role has rolinherit set */
- static bool
- has_rolinherit(Oid roleid)
- {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
- 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (HeapTupleIsValid(utup))
- 	{
- 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
- 		ReleaseSysCache(utup);
- 	}
- 	return result;
- }
- 
- 
  /*
   * Get a list of roles that the specified roleid has the privileges of
   *
--- 4817,4822 ----
*************** roles_has_privs_of(Oid roleid)
*** 4697,4703 ****
  		int			i;
  
  		/* Ignore non-inheriting roles */
! 		if (!has_rolinherit(memberid))
  			continue;
  
  		/* Find roles that memberid is directly a member of */
--- 4863,4869 ----
  		int			i;
  
  		/* Ignore non-inheriting roles */
! 		if (!has_role_attribute(memberid, ROLE_ATTR_INHERIT))
  			continue;
  
  		/* Find roles that memberid is directly a member of */
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
new file mode 100644
index 2f02303..c0d0718
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
***************
*** 33,38 ****
--- 33,39 ----
  #include "access/htup_details.h"
  #include "access/sysattr.h"
  #include "access/xact.h"
+ #include "catalog/pg_authid.h"
  #include "catalog/pg_collation.h"
  #include "catalog/pg_constraint.h"
  #include "catalog/pg_operator.h"
***************
*** 43,48 ****
--- 44,50 ----
  #include "parser/parse_coerce.h"
  #include "parser/parse_relation.h"
  #include "miscadmin.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
*************** RI_Initial_Check(Trigger *trigger, Relat
*** 2308,2314 ****
  	 * bypassrls right or is the table owner of the table(s) involved which
  	 * have RLS enabled.
  	 */
! 	if (!has_bypassrls_privilege(GetUserId()) &&
  		((pk_rel->rd_rel->relrowsecurity &&
  		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
  		 (fk_rel->rd_rel->relrowsecurity &&
--- 2310,2316 ----
  	 * bypassrls right or is the table owner of the table(s) involved which
  	 * have RLS enabled.
  	 */
! 	if (!have_role_attribute(ROLE_ATTR_BYPASSRLS) &&
  		((pk_rel->rd_rel->relrowsecurity &&
  		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
  		 (fk_rel->rd_rel->relrowsecurity &&
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
new file mode 100644
index 8fccb4c..db2a0fb
*** a/src/backend/utils/init/miscinit.c
--- b/src/backend/utils/init/miscinit.c
***************
*** 40,45 ****
--- 40,46 ----
  #include "storage/pg_shmem.h"
  #include "storage/proc.h"
  #include "storage/procarray.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/guc.h"
  #include "utils/memutils.h"
*************** SetUserIdAndContext(Oid userid, bool sec
*** 329,352 ****
  
  
  /*
-  * Check whether specified role has explicit REPLICATION privilege
-  */
- bool
- has_rolreplication(Oid roleid)
- {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
- 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (HeapTupleIsValid(utup))
- 	{
- 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication;
- 		ReleaseSysCache(utup);
- 	}
- 	return result;
- }
- 
- /*
   * Initialize user identity during normal backend startup
   */
  void
--- 330,335 ----
*************** InitializeSessionUserId(const char *role
*** 375,381 ****
  	roleid = HeapTupleGetOid(roleTup);
  
  	AuthenticatedUserId = roleid;
! 	AuthenticatedUserIsSuperuser = rform->rolsuper;
  
  	/* This sets OuterUserId/CurrentUserId too */
  	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
--- 358,364 ----
  	roleid = HeapTupleGetOid(roleTup);
  
  	AuthenticatedUserId = roleid;
! 	AuthenticatedUserIsSuperuser = (rform->rolattr & ROLE_ATTR_SUPERUSER);
  
  	/* This sets OuterUserId/CurrentUserId too */
  	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
*************** InitializeSessionUserId(const char *role
*** 394,400 ****
  		/*
  		 * Is role allowed to login at all?
  		 */
! 		if (!rform->rolcanlogin)
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  					 errmsg("role \"%s\" is not permitted to log in",
--- 377,383 ----
  		/*
  		 * Is role allowed to login at all?
  		 */
! 		if (!(rform->rolattr & ROLE_ATTR_CANLOGIN))
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  					 errmsg("role \"%s\" is not permitted to log in",
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
new file mode 100644
index c348034..268001f
*** a/src/backend/utils/init/postinit.c
--- b/src/backend/utils/init/postinit.c
*************** InitPostgres(const char *in_dbname, Oid
*** 762,768 ****
  	{
  		Assert(!bootstrap);
  
! 		if (!superuser() && !has_rolreplication(GetUserId()))
  			ereport(FATAL,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser or replication role to start walsender")));
--- 762,768 ----
  	{
  		Assert(!bootstrap);
  
! 		if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  			ereport(FATAL,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser or replication role to start walsender")));
diff --git a/src/backend/utils/misc/superuser.c b/src/backend/utils/misc/superuser.c
new file mode 100644
index ff0f947..9af77ed
*** a/src/backend/utils/misc/superuser.c
--- b/src/backend/utils/misc/superuser.c
***************
*** 22,27 ****
--- 22,28 ----
  
  #include "access/htup_details.h"
  #include "catalog/pg_authid.h"
+ #include "utils/acl.h"
  #include "utils/inval.h"
  #include "utils/syscache.h"
  #include "miscadmin.h"
*************** superuser_arg(Oid roleid)
*** 58,63 ****
--- 59,65 ----
  {
  	bool		result;
  	HeapTuple	rtup;
+ 	RoleAttr	attributes;
  
  	/* Quick out for cache hit */
  	if (OidIsValid(last_roleid) && last_roleid == roleid)
*************** superuser_arg(Oid roleid)
*** 71,77 ****
  	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(rtup))
  	{
! 		result = ((Form_pg_authid) GETSTRUCT(rtup))->rolsuper;
  		ReleaseSysCache(rtup);
  	}
  	else
--- 73,80 ----
  	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(rtup))
  	{
! 		attributes = ((Form_pg_authid) GETSTRUCT(rtup))->rolattr;
! 		result = (attributes & ROLE_ATTR_SUPERUSER);
  		ReleaseSysCache(rtup);
  	}
  	else
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
new file mode 100644
index eb633bc..f638167
*** a/src/bin/pg_dump/pg_dumpall.c
--- b/src/bin/pg_dump/pg_dumpall.c
*************** dumpRoles(PGconn *conn)
*** 671,680 ****
  	/* note: rolconfig is dumped later */
  	if (server_version >= 90500)
  		printfPQExpBuffer(buf,
! 						  "SELECT oid, rolname, rolsuper, rolinherit, "
! 						  "rolcreaterole, rolcreatedb, "
! 						  "rolcanlogin, rolconnlimit, rolpassword, "
! 						  "rolvaliduntil, rolreplication, rolbypassrls, "
  			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
  						  "rolname = current_user AS is_current_user "
  						  "FROM pg_authid "
--- 671,686 ----
  	/* note: rolconfig is dumped later */
  	if (server_version >= 90500)
  		printfPQExpBuffer(buf,
! 						  "SELECT oid, rolname, "
! 						  "pg_check_role_attribute(oid, 'SUPERUSER') AS rolsuper, "
! 						  "pg_check_role_attribute(oid, 'INHERIT') AS rolinherit, "
! 						  "pg_check_role_attribute(oid, 'CREATEROLE') AS rolcreaterole, "
! 						  "pg_check_role_attribute(oid, 'CREATEDB') AS rolcreatedb, "
! 						  "pg_check_role_attribute(oid, 'CANLOGIN') AS rolcanlogin, "
! 						  "pg_check_role_attribute(oid, 'REPLICATION') AS rolreplication, "
! 						  "pg_check_role_attribute(oid, 'BYPASSRLS') AS rolbypassrls, "
! 						  "rolconnlimit, rolpassword, "
! 						  "rolvaliduntil, "
  			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
  						  "rolname = current_user AS is_current_user "
  						  "FROM pg_authid "
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
new file mode 100644
index 3b63d2b..f28d9f4
*** a/src/include/catalog/pg_authid.h
--- b/src/include/catalog/pg_authid.h
***************
*** 45,60 ****
  CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
  {
  	NameData	rolname;		/* name of role */
! 	bool		rolsuper;		/* read this field via superuser() only! */
! 	bool		rolinherit;		/* inherit privileges from other roles? */
! 	bool		rolcreaterole;	/* allowed to create more roles? */
! 	bool		rolcreatedb;	/* allowed to create databases? */
! 	bool		rolcatupdate;	/* allowed to alter catalogs manually? */
! 	bool		rolcanlogin;	/* allowed to log in as session user? */
! 	bool		rolreplication; /* role used for streaming replication */
! 	bool		rolbypassrls;	/* allowed to bypass row level security? */
  	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
- 
  	/* remaining fields may be null; use heap_getattr to read them! */
  	text		rolpassword;	/* password, if any */
  	timestamptz rolvaliduntil;	/* password expiration time, if any */
--- 45,52 ----
  CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
  {
  	NameData	rolname;		/* name of role */
! 	int64		rolattr;		/* role attribute bitmask */
  	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
  	/* remaining fields may be null; use heap_getattr to read them! */
  	text		rolpassword;	/* password, if any */
  	timestamptz rolvaliduntil;	/* password expiration time, if any */
*************** typedef FormData_pg_authid *Form_pg_auth
*** 74,101 ****
   *		compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid					12
  #define Anum_pg_authid_rolname			1
! #define Anum_pg_authid_rolsuper			2
! #define Anum_pg_authid_rolinherit		3
! #define Anum_pg_authid_rolcreaterole	4
! #define Anum_pg_authid_rolcreatedb		5
! #define Anum_pg_authid_rolcatupdate		6
! #define Anum_pg_authid_rolcanlogin		7
! #define Anum_pg_authid_rolreplication	8
! #define Anum_pg_authid_rolbypassrls		9
! #define Anum_pg_authid_rolconnlimit		10
! #define Anum_pg_authid_rolpassword		11
! #define Anum_pg_authid_rolvaliduntil	12
  
  /* ----------------
   *		initial contents of pg_authid
   *
   * The uppercase quantities will be replaced at initdb time with
   * user choices.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t -1 _null_ _null_));
  
  #define BOOTSTRAP_SUPERUSERID 10
  
--- 66,118 ----
   *		compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid					5
  #define Anum_pg_authid_rolname			1
! #define Anum_pg_authid_rolattr			2
! #define Anum_pg_authid_rolconnlimit		3
! #define Anum_pg_authid_rolpassword		4
! #define Anum_pg_authid_rolvaliduntil	5
! 
! /* ----------------
!  * Role attributes are encoded so that we can OR them together in a bitmask.
!  * The present representation of RoleAttr (defined in acl.h) limits us to 64
!  * distinct rights.
!  * ----------------
!  */
! #define ROLE_ATTR_SUPERUSER		(1<<0)
! #define ROLE_ATTR_INHERIT		(1<<1)
! #define ROLE_ATTR_CREATEROLE	(1<<2)
! #define ROLE_ATTR_CREATEDB		(1<<3)
! #define ROLE_ATTR_CATUPDATE		(1<<4)
! #define ROLE_ATTR_CANLOGIN		(1<<5)
! #define ROLE_ATTR_REPLICATION	(1<<6)
! #define ROLE_ATTR_BYPASSRLS		(1<<7)
! #define N_ROLE_ATTRIBUTES		8		/* 1 plus the last 1<<x */
! #define ROLE_ATTR_NONE			0
! 
! /* ----------------
!  * All currently available attributes.
!  *
!  * Note: This value is currently used by genbki.pl.  Unfortunately, we have to
!  * hard code this value as we cannot use an approach like (1 << N_ROLE_ATTRIBUTES) - 1
!  * as genbki.pl simply uses the literal value associated with the #define symbol
!  * which causes an incorrect substitution. Therefore, whenever new role attributes
!  * are added this value MUST be changed as well.
!  * ----------------
!  */
! #define ROLE_ATTR_ALL          255 /* equals (1 << N_ROLE_ATTRIBUTES) - 1 */
  
  /* ----------------
   *		initial contents of pg_authid
   *
   * The uppercase quantities will be replaced at initdb time with
   * user choices.
+  *
+  * PGROLATTRALL is substituted by genbki.pl to use the value defined by
+  * ROLE_ATTR_ALL.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" PGROLATTRALL -1 _null_ _null_));
  
  #define BOOTSTRAP_SUPERUSERID 10
  
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index eace352..b4f93af
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("rank of hypothetical row without
*** 5136,5141 ****
--- 5136,5154 ----
  DATA(insert OID = 3993 ( dense_rank_final	PGNSP PGUID 12 1 0 2276 0 f f f f f f i 2 0 20 "2281 2276" "{2281,2276}" "{i,v}" _null_ _null_	hypothetical_dense_rank_final _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  
+ /* role attribute support functions */
+ DATA(insert OID = 3994 ( pg_has_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ pg_has_role_attribute_id _null_ _null_ _null_ ));
+ DESCR("check role attribute by role oid with superuser bypass check");
+ DATA(insert OID = 3995 ( pg_has_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "19 25" _null_ _null_ _null_ _null_ pg_has_role_attribute_name _null_ _null_ _null_ ));
+ DESCR("check role attribute by role name with superuser bypass check");
+ DATA(insert OID = 3996 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_id _null_ _null_ _null_ ));
+ DESCR("check role attribute by role id");
+ DATA(insert OID = 3997 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "19 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_name _null_ _null_ _null_ ));
+ DESCR("check role attribute by role name");
+ DATA(insert OID = 3998 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "20 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_attrs _null_ _null_ _null_ ));
+ DESCR("check role attribute");
+ DATA(insert OID = 3999 ( pg_all_role_attributes		PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 1009 "20" _null_ _null_ _null_ _null_ pg_all_role_attributes _null_ _null_ _null_));
+ DESCR("convert role attributes to string array");
  
  /*
   * Symbolic values for provolatile column: these indicate whether the result
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
new file mode 100644
index a8e3164..1687633
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
*************** typedef enum AclObjectKind
*** 200,205 ****
--- 200,206 ----
  	MAX_ACL_KIND				/* MUST BE LAST */
  } AclObjectKind;
  
+ typedef uint64 RoleAttr;		/* a bitmask for role attribute bits */
  
  /*
   * routines used internally
*************** extern bool pg_foreign_data_wrapper_owne
*** 326,332 ****
  extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
  extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
  extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
! extern bool has_createrole_privilege(Oid roleid);
! extern bool has_bypassrls_privilege(Oid roleid);
  
  #endif   /* ACL_H */
--- 327,336 ----
  extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
  extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
  extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
! 
! /* role attribute check routines */
! extern bool has_role_attribute(Oid roleid, RoleAttr attribute);
! extern bool have_role_attribute(RoleAttr attribute);
! extern bool check_role_attribute(Oid roleid, RoleAttr attribute);
  
  #endif   /* ACL_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index 2da3002..c8e0e3a
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum pg_has_role_id_name(PG_FUNC
*** 106,111 ****
--- 106,117 ----
  extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS);
  extern Datum pg_has_role_name(PG_FUNCTION_ARGS);
  extern Datum pg_has_role_id(PG_FUNCTION_ARGS);
+ extern Datum pg_has_role_attribute_id(PG_FUNCTION_ARGS);
+ extern Datum pg_has_role_attribute_name(PG_FUNCTION_ARGS);
+ extern Datum pg_check_role_attribute_id(PG_FUNCTION_ARGS);
+ extern Datum pg_check_role_attribute_name(PG_FUNCTION_ARGS);
+ extern Datum pg_check_role_attribute_attrs(PG_FUNCTION_ARGS);
+ extern Datum pg_all_role_attributes(PG_FUNCTION_ARGS);
  
  /* bool.c */
  extern Datum boolin(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
new file mode 100644
index 80c3351..d105215
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
*************** pg_group| SELECT pg_authid.rolname AS gr
*** 1314,1320 ****
             FROM pg_auth_members
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
!   WHERE (NOT pg_authid.rolcanlogin);
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,
--- 1314,1320 ----
             FROM pg_auth_members
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
!   WHERE (NOT pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN'::text));
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,
*************** pg_replication_slots| SELECT l.slot_name
*** 1405,1421 ****
     FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, xmin, catalog_xmin, restart_lsn)
       LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
  pg_roles| SELECT pg_authid.rolname,
!     pg_authid.rolsuper,
!     pg_authid.rolinherit,
!     pg_authid.rolcreaterole,
!     pg_authid.rolcreatedb,
!     pg_authid.rolcatupdate,
!     pg_authid.rolcanlogin,
!     pg_authid.rolreplication,
      pg_authid.rolconnlimit,
      '********'::text AS rolpassword,
      pg_authid.rolvaliduntil,
-     pg_authid.rolbypassrls,
      s.setconfig AS rolconfig,
      pg_authid.oid
     FROM (pg_authid
--- 1405,1421 ----
     FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, xmin, catalog_xmin, restart_lsn)
       LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
  pg_roles| SELECT pg_authid.rolname,
!     pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER'::text) AS rolsuper,
!     pg_check_role_attribute(pg_authid.rolattr, 'INHERIT'::text) AS rolinherit,
!     pg_check_role_attribute(pg_authid.rolattr, 'CREATEROLE'::text) AS rolcreaterole,
!     pg_check_role_attribute(pg_authid.rolattr, 'CREATEDB'::text) AS rolcreatedb,
!     pg_check_role_attribute(pg_authid.rolattr, 'CATUPDATE'::text) AS rolcatupdate,
!     pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN'::text) AS rolcanlogin,
!     pg_check_role_attribute(pg_authid.rolattr, 'REPLICATION'::text) AS rolreplication,
!     pg_check_role_attribute(pg_authid.rolattr, 'BYPASSRLS'::text) AS rolbypassrls,
      pg_authid.rolconnlimit,
      '********'::text AS rolpassword,
      pg_authid.rolvaliduntil,
      s.setconfig AS rolconfig,
      pg_authid.oid
     FROM (pg_authid
*************** pg_settings| SELECT a.name,
*** 1608,1623 ****
     FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline);
  pg_shadow| SELECT pg_authid.rolname AS usename,
      pg_authid.oid AS usesysid,
!     pg_authid.rolcreatedb AS usecreatedb,
!     pg_authid.rolsuper AS usesuper,
!     pg_authid.rolcatupdate AS usecatupd,
!     pg_authid.rolreplication AS userepl,
      pg_authid.rolpassword AS passwd,
      (pg_authid.rolvaliduntil)::abstime AS valuntil,
      s.setconfig AS useconfig
     FROM (pg_authid
       LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
!   WHERE pg_authid.rolcanlogin;
  pg_stat_activity| SELECT s.datid,
      d.datname,
      s.pid,
--- 1608,1623 ----
     FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline);
  pg_shadow| SELECT pg_authid.rolname AS usename,
      pg_authid.oid AS usesysid,
!     pg_check_role_attribute(pg_authid.rolattr, 'CREATEDB'::text) AS usecreatedb,
!     pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER'::text) AS usesuper,
!     pg_check_role_attribute(pg_authid.rolattr, 'CATUPDATE'::text) AS usecatupd,
!     pg_check_role_attribute(pg_authid.rolattr, 'REPLICATION'::text) AS userepl,
      pg_authid.rolpassword AS passwd,
      (pg_authid.rolvaliduntil)::abstime AS valuntil,
      s.setconfig AS useconfig
     FROM (pg_authid
       LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
!   WHERE pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN'::text);
  pg_stat_activity| SELECT s.datid,
      d.datname,
      s.pid,
#28Stephen Frost
sfrost@snowman.net
In reply to: Adam Brightwell (#27)
Re: Role Attribute Bitmask Catalog Representation

Adam,

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

I have attached an updated patch with initial documentation changes for
review.

Awesome, thanks.

I'm going to continue looking at this in more detail, but wanted to
mention a few things I noticed in the documentation changes:

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
--- 1391,1493 ----
</row>

<row>
! <entry><structfield>rolattr</structfield></entry>
! <entry><type>bigint</type></entry>
! <entry>
! Role attributes; see <xref linkend="sql-createrole"> for details
! </entry>
! </row>

Shouldn't this refer to the table below (which I like..)? Or perhaps to
both the table and sql-createrole? Have you had a chance to actually
build the docs and look at the results to see how things look? I should
have time later tomorrow to do that and look over the results also, but
figured I'd ask.

! <table id="catalog-rolattr-bitmap-table">
! <title><structfield>rolattr</> bitmap positions</title>

I would call this 'Attributes in rolattr' or similar rather than 'bitmap
positions'.

! <tgroup cols="3">
! <thead>
! <row>
! <entry>Position</entry>
! <entry>Attribute</entry>
! <entry>Description</entry>
! </row>
! </thead>

There should be a column for 'Option' or something- basically, a clear
tie-back to what CREATE ROLE refers to these as. I'm thinking that
reordering the columns would help here, consider:

Attribute (using the 'Superuser', 'Inherit', etc 'nice' names)
CREATE ROLE Clause (note: that's how CREATE ROLE describes the terms)
Description
Position

! <tbody>
! <row>
! <entry><literal>0</literal></entry>
! <entry>Superuser</entry>
<entry>Role has superuser privileges</entry>
</row>

<row>
! <entry><literal>1</literal></entry>
! <entry>Inherit</entry>
! <entry>Role automatically inherits privileges of roles it is a member of</entry>
</row>

This doesn't follow our general convention to wrap lines in the SGML at
80 chars (same as the C code) and, really, if you fix that it looks like
these lines shouldn't even be different at all (see above with the 'Role
has superuser privileges' <entry></entry> line).

Same is true for a few of the other cases.

<row>
! <entry><literal>4</literal></entry>
! <entry>Catalog Update</entry>
<entry>
! Role can update system catalogs directly. (Even a superuser cannot do this unless this column is true)
</entry>
</row>

I'm really not sure what to do with this one. I don't like leaving it
as-is, but I suppose it's probably the right thing for this patch to do.
Perhaps another patch should be proposed which improves the
documentation around rolcatupdate and its relationship to superuser.

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
new file mode 100644
index ef69b94..74bc702
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
+    <para>
+     <function>pg_has_role_attribute</function> checks the attribute permissions
+     given to a role.  It will always return <literal>true</literal> for roles
+     with superuser privileges unless the attribute being checked is
+     <literal>CATUPDATE</literal> (superuser cannot bypass
+     <literal>CATUPDATE</literal> permissions). The role can be specified by name
+     and by OID. The attribute is specified by a text string which must evaluate
+     to one of the following role attributes:
+     <literal>SUPERUSER</literal>,
+     <literal>INHERIT</literal>,
+     <literal>CREATEROLE</literal>,
+     <literal>CREATEDB</literal>,
+     <literal>CATUPDATE</literal>,
+     <literal>CANLOGIN</literal>,
+     <literal>REPLICATION</literal>, or
+     <literal>BYPASSRLS</literal>.

This should probably refer to CREATE ROLE also as being where the
meaning of these strings is defined.

Otherwise, the docs look pretty good to me.

Thanks!

Stephen

#29Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#28)
Re: Role Attribute Bitmask Catalog Representation

FWIW I've been giving this patch a look and and adjusting some coding
details here and there. Do you intend to commit it yourself? You're
not listed as reviewer or committer for it in the commitfest app, FWIW.

One thing I don't very much like is that check_role_attribute() receives
a RoleAttr but nowhere it checks that only one bit is set in
'attribute'. From the coding, this routine would return true if just
one of those bits is set, which is probably not what is intended. Now I
realize that current callers all pass a bitmask with a single bit set,
but I think it'd be better to have some protection against that, for
possible future coding mistakes.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#29)
Re: Role Attribute Bitmask Catalog Representation

Alvaro,

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

FWIW I've been giving this patch a look and and adjusting some coding
details here and there. Do you intend to commit it yourself? You're
not listed as reviewer or committer for it in the commitfest app, FWIW.

Oh, great, thanks! And, yeah, I'm not as good as I should be about
updating the commitfest app. As for committing it, I was thinking I
would but you're certainly welcome to if you're interested. I would
like this to be committed soonish as it will then allow Adam to rebase
the patch which adds the various role attributes discussed previously on
top of the representation changes. I suspect he's done some work in
that direction already, but I keep asking for changes to this patch
which would then ripple down into the other.

One thing I don't very much like is that check_role_attribute() receives
a RoleAttr but nowhere it checks that only one bit is set in
'attribute'. From the coding, this routine would return true if just
one of those bits is set, which is probably not what is intended. Now I
realize that current callers all pass a bitmask with a single bit set,
but I think it'd be better to have some protection against that, for
possible future coding mistakes.

That's certainly a good point. I'm inclined to suggest that there be an
Assert() check in check_role_attribute(), or were you thinking of
something else..?

Thanks!

Stephen

#31Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#30)
Re: Role Attribute Bitmask Catalog Representation

Stephen Frost wrote:

Alvaro,

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

FWIW I've been giving this patch a look and and adjusting some coding
details here and there. Do you intend to commit it yourself? You're
not listed as reviewer or committer for it in the commitfest app, FWIW.

Oh, great, thanks! And, yeah, I'm not as good as I should be about
updating the commitfest app. As for committing it, I was thinking I
would but you're certainly welcome to if you're interested. I would
like this to be committed soonish as it will then allow Adam to rebase
the patch which adds the various role attributes discussed previously on
top of the representation changes. I suspect he's done some work in
that direction already, but I keep asking for changes to this patch
which would then ripple down into the other.

Sure, I will go over it a bit more and merge changes from Adam to the
docs as they come through, and commit soon.

One thing I don't very much like is that check_role_attribute() receives
a RoleAttr but nowhere it checks that only one bit is set in
'attribute'. From the coding, this routine would return true if just
one of those bits is set, which is probably not what is intended. Now I
realize that current callers all pass a bitmask with a single bit set,
but I think it'd be better to have some protection against that, for
possible future coding mistakes.

That's certainly a good point. I'm inclined to suggest that there be an
Assert() check in check_role_attribute(), or were you thinking of
something else..?

Yeah, an Assert() is what I had in mind.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#32Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#29)
Re: Role Attribute Bitmask Catalog Representation

The fact that the ROLE_ATTR_* definitions are in pg_authid.h means that
there are now a lot of files that need to include that one. I think the
includes relative to ACLs and roles is rather messy now, and this patch
makes it a bit worse.

I think we should create a new header file (maybe acltypes.h or
acldefs.h), which only contains the AclMode and RoleAttr typedefs and
their associated defines; that one would be included from parsenodes.h,
acl.h and pg_authid.h. Everything else would be in acl.h. So code that
currently checks permissions using only acl.h do not need any extra
includes.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#32)
2 attachment(s)
Re: Role Attribute Bitmask Catalog Representation

Alvaro Herrera wrote:

The fact that the ROLE_ATTR_* definitions are in pg_authid.h means that
there are now a lot of files that need to include that one. I think the
includes relative to ACLs and roles is rather messy now, and this patch
makes it a bit worse.

I think we should create a new header file (maybe acltypes.h or
acldefs.h), which only contains the AclMode and RoleAttr typedefs and
their associated defines; that one would be included from parsenodes.h,
acl.h and pg_authid.h. Everything else would be in acl.h. So code that
currently checks permissions using only acl.h do not need any extra
includes.

I propose this patch on top of Adam's v5. Also included is a full patch
against master.

Unrelated: when changing from unified to context format, I saw
filterdiff fail to produce a complete patch, skipping some hunks in its
output. My first impression was that I had dropped some hunks in git,
so I wasted some time comparing v5 and v6 by hand before remembering
that Michael Paquier had mentioned this problem previously.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

role-attribute-bitmask-v5a.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 8475985..3181a79 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -22,7 +22,6 @@
 #include "access/xlog_internal.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
-#include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
 #include "miscadmin.h"
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a403c64..a6de2ff 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -28,7 +28,7 @@ all: $(BKIFILES) schemapg.h
 # indexing.h had better be last, and toasting.h just before it.
 
 POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
-	pg_proc.h pg_type.h pg_attribute.h pg_class.h \
+	acldefs.h pg_proc.h pg_type.h pg_attribute.h pg_class.h \
 	pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
 	pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
 	pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 4663429..21d282c 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -5038,18 +5038,18 @@ pg_extension_ownercheck(Oid ext_oid, Oid roleid)
  * roleid - the oid of the role to check.
  * attribute - the attribute to check.
  *
- * Note: Use this function for role attribute permission checking as it accounts
- * for superuser status.  It will always return true for roles with superuser
- * privileges unless the attribute being checked is CATUPDATE (superusers are not
- * allowed to bypass CATUPDATE permissions).
+ * Note: Use this function for role attribute permission checking as it
+ * accounts for superuser status.  It will always return true for roles with
+ * superuser privileges unless the attribute being checked is CATUPDATE
+ * (superusers are not allowed to bypass CATUPDATE permissions).
  *
- * Note: roles do not have owners per se; instead we use this test in
- * places where an ownership-like permissions test is needed for a role.
- * Be sure to apply it to the role trying to do the operation, not the
- * role being operated on!  Also note that this generally should not be
- * considered enough privilege if the target role is a superuser.
- * (We don't handle that consideration here because we want to give a
- * separate error message for such cases, so the caller has to deal with it.)
+ * Note: roles do not have owners per se; instead we use this test in places
+ * where an ownership-like permissions test is needed for a role.  Be sure to
+ * apply it to the role trying to do the operation, not the role being operated
+ * on!  Also note that this generally should not be considered enough privilege
+ * if the target role is a superuser.  (We don't handle that consideration here
+ * because we want to give a separate error message for such cases, so the
+ * caller has to deal with it.)
  */
 bool
 has_role_attribute(Oid roleid, RoleAttr attribute)
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index 2929b66..415ac17 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -91,7 +91,7 @@ my $BOOTSTRAP_SUPERUSERID =
 my $PG_CATALOG_NAMESPACE =
   find_defined_symbol('pg_namespace.h', 'PG_CATALOG_NAMESPACE');
 my $ROLE_ATTR_ALL =
-  find_defined_symbol('pg_authid.h', 'ROLE_ATTR_ALL');
+  find_defined_symbol('acldefs.h', 'ROLE_ATTR_ALL');
 
 # Read all the input header files into internal data structures
 my $catalogs = Catalog::Catalogs(@input_files);
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 5bde610..564f77a 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -73,14 +73,7 @@ CreateRole(CreateRoleStmt *stmt)
 	char	   *password = NULL;	/* user password */
 	bool		encrypt_password = Password_encryption; /* encrypt password? */
 	char		encrypted_password[MD5_PASSWD_LEN + 1];
-	bool		issuper = false;	/* Make the user a superuser? */
-	bool		inherit = true; /* Auto inherit privileges? */
-	bool		createrole = false;		/* Can this user create roles? */
-	bool		createdb = false;		/* Can the user create databases? */
-	bool		canlogin = false;		/* Can this user login? */
-	bool		isreplication = false;	/* Is this a replication role? */
-	bool		bypassrls = false;		/* Is this a row security enabled role? */
-	RoleAttr	attributes = ROLE_ATTR_NONE;	/* role attributes, initialized to none. */
+	RoleAttr	attributes;
 	int			connlimit = -1; /* maximum connections allowed */
 	List	   *addroleto = NIL;	/* roles to make this a member of */
 	List	   *rolemembers = NIL;		/* roles to be members of this role */
@@ -102,13 +95,17 @@ CreateRole(CreateRoleStmt *stmt)
 	DefElem    *dvalidUntil = NULL;
 	DefElem    *dbypassRLS = NULL;
 
-	/* The defaults can vary depending on the original statement type */
+	/*
+	 * Every role has INHERIT by default, and CANLOGIN depends on the statement
+	 * type.
+	 */
+	attributes = ROLE_ATTR_INHERIT;
 	switch (stmt->stmt_type)
 	{
 		case ROLESTMT_ROLE:
 			break;
 		case ROLESTMT_USER:
-			canlogin = true;
+			attributes |= ROLE_ATTR_CANLOGIN;
 			/* may eventually want inherit to default to false here */
 			break;
 		case ROLESTMT_GROUP:
@@ -243,21 +240,74 @@ CreateRole(CreateRoleStmt *stmt)
 	if (dpassword && dpassword->arg)
 		password = strVal(dpassword->arg);
 
-	/* Role Attributes */
+	/* Set up role attributes and check permissions to set each of them */
 	if (dissuper)
-		issuper = intVal(dissuper->arg) != 0;
+	{
+		if (intVal(dissuper->arg) != 0)
+		{
+			if (!superuser())
+				ereport(ERROR,
+						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						 errmsg("must be superuser to create superusers")));
+			attributes |= ROLE_ATTR_SUPERUSER;
+		}
+		else
+			attributes &= ~ROLE_ATTR_SUPERUSER;
+	}
 	if (dinherit)
-		inherit = intVal(dinherit->arg) != 0;
+	{
+		if (intVal(dinherit->arg) != 0)
+			attributes |= ROLE_ATTR_INHERIT;
+		else
+			attributes &= ~ROLE_ATTR_INHERIT;
+	}
 	if (dcreaterole)
-		createrole = intVal(dcreaterole->arg) != 0;
+	{
+		if (intVal(dcreaterole->arg) != 0)
+			attributes |= ROLE_ATTR_CREATEROLE;
+		else
+			attributes &= ~ROLE_ATTR_CREATEROLE;
+	}
 	if (dcreatedb)
-		createdb = intVal(dcreatedb->arg) != 0;
+	{
+		if (intVal(dcreatedb->arg) != 0)
+			attributes |= ROLE_ATTR_CREATEDB;
+		else
+			attributes &= ~ROLE_ATTR_CREATEDB;
+	}
 	if (dcanlogin)
-		canlogin = intVal(dcanlogin->arg) != 0;
+	{
+		if (intVal(dcanlogin->arg) != 0)
+			attributes |= ROLE_ATTR_CANLOGIN;
+		else
+			attributes &= ~ROLE_ATTR_CANLOGIN;
+	}
 	if (disreplication)
-		isreplication = intVal(disreplication->arg) != 0;
+	{
+		if (intVal(disreplication->arg) != 0)
+		{
+			if (!superuser())
+				ereport(ERROR,
+						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						 errmsg("must be superuser to create replication users")));
+			attributes |= ROLE_ATTR_REPLICATION;
+		}
+		else
+			attributes &= ~ROLE_ATTR_REPLICATION;
+	}
 	if (dbypassRLS)
-		bypassrls = intVal(dbypassRLS->arg) != 0;
+	{
+		if (intVal(dbypassRLS->arg) != 0)
+		{
+			if (!superuser())
+				ereport(ERROR,
+						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						 errmsg("must be superuser to change bypassrls attribute")));
+			attributes |= ROLE_ATTR_BYPASSRLS;
+		}
+		else
+			attributes &= ~ROLE_ATTR_BYPASSRLS;
+	}
 
 	if (dconnlimit)
 	{
@@ -276,35 +326,11 @@ CreateRole(CreateRoleStmt *stmt)
 	if (dvalidUntil)
 		validUntil = strVal(dvalidUntil->arg);
 
-	/* Check some permissions first */
-	if (issuper)
-	{
-		if (!superuser())
-			ereport(ERROR,
-					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to create superusers")));
-	}
-	else if (isreplication)
-	{
-		if (!superuser())
-			ereport(ERROR,
-					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				   errmsg("must be superuser to create replication users")));
-	}
-	else if (bypassrls)
-	{
-		if (!superuser())
-			ereport(ERROR,
-					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to change bypassrls attribute.")));
-	}
-	else
-	{
-		if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
-			ereport(ERROR,
-					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("permission denied to create role")));
-	}
+	/* Check permissions */
+	if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied to create role")));
 
 	if (strcmp(stmt->role, "public") == 0 ||
 		strcmp(stmt->role, "none") == 0)
@@ -351,22 +377,6 @@ CreateRole(CreateRoleStmt *stmt)
 								validUntil_datum,
 								validUntil_null);
 
-	/* Set all role attributes */
-	if (issuper)
-		attributes |= ROLE_ATTR_SUPERUSER;
-	if (inherit)
-		attributes |= ROLE_ATTR_INHERIT;
-	if (createrole)
-		attributes |= ROLE_ATTR_CREATEROLE;
-	if (createdb)
-		attributes |= ROLE_ATTR_CREATEDB;
-	if (canlogin)
-		attributes |= ROLE_ATTR_CANLOGIN;
-	if (isreplication)
-		attributes |= ROLE_ATTR_REPLICATION;
-	if (bypassrls)
-		attributes |= ROLE_ATTR_BYPASSRLS;
-
 	/*
 	 * Build a tuple to insert
 	 */
@@ -663,8 +673,8 @@ AlterRole(AlterRoleStmt *stmt)
 	roleid = HeapTupleGetOid(tuple);
 
 	/*
-	 * To mess with a superuser you gotta be superuser; else you need
-	 * createrole, or just want to change your own password
+	 * To mess with a superuser or a replication user you gotta be superuser;
+	 * else you need createrole, or just want to change your own password
 	 */
 
 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
@@ -751,50 +761,64 @@ AlterRole(AlterRoleStmt *stmt)
 	 */
 	if (issuper >= 0)
 	{
-		attributes = (issuper > 0) ? attributes | (ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE)
-								   : attributes & ~(ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE);
+		if (issuper > 0)
+			attributes |= ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE;
+		else
+			attributes &= ~(ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE);
 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
 	if (inherit >= 0)
 	{
-		attributes = (inherit > 0) ? attributes | ROLE_ATTR_INHERIT
-								   : attributes & ~(ROLE_ATTR_INHERIT);
+		if (inherit > 0)
+			attributes |= ROLE_ATTR_INHERIT;
+		else
+			attributes &= ~ROLE_ATTR_INHERIT;
 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
 	if (createrole >= 0)
 	{
-		attributes = (createrole > 0) ? attributes | ROLE_ATTR_CREATEROLE
-									  : attributes & ~(ROLE_ATTR_CREATEROLE);
+		if (createrole > 0)
+			attributes |= ROLE_ATTR_CREATEROLE;
+		else
+			attributes &= ~ROLE_ATTR_CREATEROLE;
 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
 	if (createdb >= 0)
 	{
-		attributes = (createdb > 0) ? attributes | ROLE_ATTR_CREATEDB
-									: attributes & ~(ROLE_ATTR_CREATEDB);
+		if (createdb > 0)
+			attributes |= ROLE_ATTR_CREATEDB;
+		else
+			attributes &= ~ROLE_ATTR_CREATEDB;
 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
 	if (canlogin >= 0)
 	{
-		attributes = (canlogin > 0) ? attributes | ROLE_ATTR_CANLOGIN
-									: attributes & ~(ROLE_ATTR_CANLOGIN);
+		if (canlogin > 0)
+			attributes |= ROLE_ATTR_CANLOGIN;
+		else
+			attributes &= ~ROLE_ATTR_CANLOGIN;
 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
 	if (isreplication >= 0)
 	{
-		attributes = (isreplication > 0) ? attributes | ROLE_ATTR_REPLICATION
-										 : attributes & ~(ROLE_ATTR_REPLICATION);
+		if (isreplication > 0)
+			attributes |= ROLE_ATTR_REPLICATION;
+		else
+			attributes &= ~ROLE_ATTR_REPLICATION;
 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
 	if (bypassrls >= 0)
 	{
-		attributes = (bypassrls > 0) ? attributes | ROLE_ATTR_BYPASSRLS
-										 : attributes & ~(ROLE_ATTR_BYPASSRLS);
+		if (bypassrls > 0)
+			attributes |= ROLE_ATTR_BYPASSRLS;
+		else
+			attributes &= ~ROLE_ATTR_BYPASSRLS;
 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 5f1126e..1a38f56 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -17,19 +17,13 @@
 
 #include <unistd.h>
 
+#include "access/xlog_internal.h"
+#include "catalog/pg_type.h"
 #include "fmgr.h"
 #include "funcapi.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
-
-#include "access/xlog_internal.h"
-
-#include "catalog/pg_authid.h"
-#include "catalog/pg_type.h"
-
 #include "nodes/makefuncs.h"
-
-#include "mb/pg_wchar.h"
-
 #include "utils/acl.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -38,11 +32,9 @@
 #include "utils/pg_lsn.h"
 #include "utils/resowner.h"
 #include "utils/lsyscache.h"
-
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/logicalfuncs.h"
-
 #include "storage/fd.h"
 
 /* private date for writing out data */
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index bc6a23a..c113a0b 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -17,7 +17,6 @@
 #include "miscadmin.h"
 
 #include "access/htup_details.h"
-#include "catalog/pg_authid.h"
 #include "replication/slot.h"
 #include "replication/logical.h"
 #include "replication/logicalfuncs.h"
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index 58633cc..f41ad34 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -36,7 +36,6 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/sysattr.h"
-#include "catalog/pg_authid.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_policy.h"
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 93017b2..4c03955 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -4723,9 +4723,7 @@ pg_all_role_attributes(PG_FUNCTION_ARGS)
 	int				i = 0;
 
 	/*
-	 * If no attributes are assigned, then there is no need to go through the
-	 * individual checks for which are assigned.  Therefore, we can short circuit
-	 * and return an empty array.
+	 * Short-circuit the case for no attributes assigned.
 	 */
 	if (attributes == ROLE_ATTR_NONE)
 		PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));
@@ -4734,21 +4732,21 @@ pg_all_role_attributes(PG_FUNCTION_ARGS)
 
 	/* Determine which attributes are assigned. */
 	if (attributes & ROLE_ATTR_SUPERUSER)
-		temp_array[i++] = CStringGetTextDatum("Superuser");
+		temp_array[i++] = CStringGetTextDatum(_("Superuser"));
 	if (attributes & ROLE_ATTR_INHERIT)
-		temp_array[i++] = CStringGetTextDatum("Inherit");
+		temp_array[i++] = CStringGetTextDatum(_("Inherit"));
 	if (attributes & ROLE_ATTR_CREATEROLE)
-		temp_array[i++] = CStringGetTextDatum("Create Role");
+		temp_array[i++] = CStringGetTextDatum(_("Create Role"));
 	if (attributes & ROLE_ATTR_CREATEDB)
-		temp_array[i++] = CStringGetTextDatum("Create DB");
+		temp_array[i++] = CStringGetTextDatum(_("Create DB"));
 	if (attributes & ROLE_ATTR_CATUPDATE)
-		temp_array[i++] = CStringGetTextDatum("Catalog Update");
+		temp_array[i++] = CStringGetTextDatum(_("Catalog Update"));
 	if (attributes & ROLE_ATTR_CANLOGIN)
-		temp_array[i++] = CStringGetTextDatum("Login");
+		temp_array[i++] = CStringGetTextDatum(_("Login"));
 	if (attributes & ROLE_ATTR_REPLICATION)
-		temp_array[i++] = CStringGetTextDatum("Replication");
+		temp_array[i++] = CStringGetTextDatum(_("Replication"));
 	if (attributes & ROLE_ATTR_BYPASSRLS)
-		temp_array[i++] = CStringGetTextDatum("Bypass RLS");
+		temp_array[i++] = CStringGetTextDatum(_("Bypass RLS"));
 
 	result = construct_array(temp_array, i, TEXTOID, -1, false, 'i');
 
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index efbcc71..ccb1066 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -33,7 +33,6 @@
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
-#include "catalog/pg_authid.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_operator.h"
@@ -44,7 +43,6 @@
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "miscadmin.h"
-#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
diff --git a/src/backend/utils/misc/superuser.c b/src/backend/utils/misc/superuser.c
index 9af77ed..67d070c 100644
--- a/src/backend/utils/misc/superuser.c
+++ b/src/backend/utils/misc/superuser.c
@@ -22,7 +22,6 @@
 
 #include "access/htup_details.h"
 #include "catalog/pg_authid.h"
-#include "utils/acl.h"
 #include "utils/inval.h"
 #include "utils/syscache.h"
 #include "miscadmin.h"
diff --git a/src/include/catalog/acldefs.h b/src/include/catalog/acldefs.h
new file mode 100644
index 0000000..2dcc174
--- /dev/null
+++ b/src/include/catalog/acldefs.h
@@ -0,0 +1,72 @@
+/*-------------------------------------------------------------------------
+ *
+ * acldefs.h
+ *	  base definitions for ACLs and role attributes
+ *
+ * Portions Copyright (c) 2014, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/acldefs.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACLDEFS_H
+#define ACLDEFS_H
+
+/*
+ * Grantable rights are encoded so that we can OR them together in a bitmask.
+ * The present representation of AclItem limits us to 16 distinct rights,
+ * even though AclMode is defined as uint32.  See utils/acl.h.
+ *
+ * Caution: changing these codes breaks stored ACLs, hence forces initdb.
+ */
+typedef uint32 AclMode;			/* a bitmask of privilege bits */
+
+#define ACL_INSERT		(1<<0)	/* for relations */
+#define ACL_SELECT		(1<<1)
+#define ACL_UPDATE		(1<<2)
+#define ACL_DELETE		(1<<3)
+#define ACL_TRUNCATE	(1<<4)
+#define ACL_REFERENCES	(1<<5)
+#define ACL_TRIGGER		(1<<6)
+#define ACL_EXECUTE		(1<<7)	/* for functions */
+#define ACL_USAGE		(1<<8)	/* for languages, namespaces, FDWs, and
+								 * servers */
+#define ACL_CREATE		(1<<9)	/* for namespaces and databases */
+#define ACL_CREATE_TEMP (1<<10) /* for databases */
+#define ACL_CONNECT		(1<<11) /* for databases */
+#define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
+#define ACL_NO_RIGHTS	0
+/* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
+#define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
+
+#define ACL_ID_PUBLIC	0		/* placeholder for id in a PUBLIC acl item */
+
+
+/*
+ * Role attributes are encoded so that we can OR them together in a bitmask.
+ * The present representation of RoleAttr (defined in acl.h) limits us to 64
+ * distinct rights.
+ *
+ * Note about ROLE_ATTR_ALL: This symbol is used verbatim by genbki.pl, which
+ * means we need to hard-code its value instead of using a symbolic definition.
+ * Therefore, whenever role attributes are changed, this value MUST be updated
+ * manually.
+ */
+
+/* A bitmask for role attributes */
+typedef uint64 RoleAttr;
+
+#define ROLE_ATTR_NONE			0
+#define ROLE_ATTR_SUPERUSER		(1<<0)
+#define ROLE_ATTR_INHERIT		(1<<1)
+#define ROLE_ATTR_CREATEROLE	(1<<2)
+#define ROLE_ATTR_CREATEDB		(1<<3)
+#define ROLE_ATTR_CATUPDATE		(1<<4)
+#define ROLE_ATTR_CANLOGIN		(1<<5)
+#define ROLE_ATTR_REPLICATION	(1<<6)
+#define ROLE_ATTR_BYPASSRLS		(1<<7)
+#define N_ROLE_ATTRIBUTES		8		/* 1 plus the last 1<<x */
+#define ROLE_ATTR_ALL			255		/* (1 << N_ROLE_ATTRIBUTES) - 1 */
+
+
+#endif   /* ACLDEFS_H */
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index f28d9f4..a45f38d 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -21,6 +21,7 @@
 #ifndef PG_AUTHID_H
 #define PG_AUTHID_H
 
+#include "catalog/acldefs.h"
 #include "catalog/genbki.h"
 
 /*
@@ -73,34 +74,6 @@ typedef FormData_pg_authid *Form_pg_authid;
 #define Anum_pg_authid_rolpassword		4
 #define Anum_pg_authid_rolvaliduntil	5
 
-/* ----------------
- * Role attributes are encoded so that we can OR them together in a bitmask.
- * The present representation of RoleAttr (defined in acl.h) limits us to 64
- * distinct rights.
- * ----------------
- */
-#define ROLE_ATTR_SUPERUSER		(1<<0)
-#define ROLE_ATTR_INHERIT		(1<<1)
-#define ROLE_ATTR_CREATEROLE	(1<<2)
-#define ROLE_ATTR_CREATEDB		(1<<3)
-#define ROLE_ATTR_CATUPDATE		(1<<4)
-#define ROLE_ATTR_CANLOGIN		(1<<5)
-#define ROLE_ATTR_REPLICATION	(1<<6)
-#define ROLE_ATTR_BYPASSRLS		(1<<7)
-#define N_ROLE_ATTRIBUTES		8		/* 1 plus the last 1<<x */
-#define ROLE_ATTR_NONE			0
-
-/* ----------------
- * All currently available attributes.
- *
- * Note: This value is currently used by genbki.pl.  Unfortunately, we have to
- * hard code this value as we cannot use an approach like (1 << N_ROLE_ATTRIBUTES) - 1
- * as genbki.pl simply uses the literal value associated with the #define symbol
- * which causes an incorrect substitution. Therefore, whenever new role attributes
- * are added this value MUST be changed as well.
- * ----------------
- */
-#define ROLE_ATTR_ALL          255 /* equals (1 << N_ROLE_ATTRIBUTES) - 1 */
 
 /* ----------------
  *		initial contents of pg_authid
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 458eeb0..ecb5780 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -23,8 +23,10 @@
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 #include "nodes/value.h"
+#include "catalog/acldefs.h"
 #include "utils/lockwaitpolicy.h"
 
+
 /* Possible sources of a Query */
 typedef enum QuerySource
 {
@@ -51,33 +53,6 @@ typedef enum SortByNulls
 	SORTBY_NULLS_LAST
 } SortByNulls;
 
-/*
- * Grantable rights are encoded so that we can OR them together in a bitmask.
- * The present representation of AclItem limits us to 16 distinct rights,
- * even though AclMode is defined as uint32.  See utils/acl.h.
- *
- * Caution: changing these codes breaks stored ACLs, hence forces initdb.
- */
-typedef uint32 AclMode;			/* a bitmask of privilege bits */
-
-#define ACL_INSERT		(1<<0)	/* for relations */
-#define ACL_SELECT		(1<<1)
-#define ACL_UPDATE		(1<<2)
-#define ACL_DELETE		(1<<3)
-#define ACL_TRUNCATE	(1<<4)
-#define ACL_REFERENCES	(1<<5)
-#define ACL_TRIGGER		(1<<6)
-#define ACL_EXECUTE		(1<<7)	/* for functions */
-#define ACL_USAGE		(1<<8)	/* for languages, namespaces, FDWs, and
-								 * servers */
-#define ACL_CREATE		(1<<9)	/* for namespaces and databases */
-#define ACL_CREATE_TEMP (1<<10) /* for databases */
-#define ACL_CONNECT		(1<<11) /* for databases */
-#define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
-#define ACL_NO_RIGHTS	0
-/* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
-#define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
-
 
 /*****************************************************************************
  *	Query Tree
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 1687633..4e8d81c 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -30,13 +30,6 @@
 
 
 /*
- * typedef AclMode is declared in parsenodes.h, also the individual privilege
- * bit meanings are defined there
- */
-
-#define ACL_ID_PUBLIC	0		/* placeholder for id in a PUBLIC acl item */
-
-/*
  * AclItem
  *
  * Note: must be same size on all platforms, because the size is hardcoded
@@ -200,7 +193,6 @@ typedef enum AclObjectKind
 	MAX_ACL_KIND				/* MUST BE LAST */
 } AclObjectKind;
 
-typedef uint64 RoleAttr;		/* a bitmask for role attribute bits */
 
 /*
  * routines used internally
role-attribute-bitmask-v6.patchtext/x-diff; charset=us-asciiDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9ceb96b..b0b4fca 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1391,51 +1391,103 @@
      </row>
 
      <row>
-      <entry><structfield>rolsuper</structfield></entry>
-      <entry><type>bool</type></entry>
+      <entry><structfield>rolattr</structfield></entry>
+      <entry><type>bigint</type></entry>
+      <entry>
+       Role attributes; see <xref linkend="sql-createrole"> for details
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>rolconnlimit</structfield></entry>
+      <entry><type>int4</type></entry>
+      <entry>
+       For roles that can log in, this sets maximum number of concurrent
+       connections this role can make.  -1 means no limit.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>rolpassword</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry>
+       Password (possibly encrypted); null if none.  If the password
+       is encrypted, this column will begin with the string <literal>md5</>
+       followed by a 32-character hexadecimal MD5 hash.  The MD5 hash
+       will be of the user's password concatenated to their user name.
+       For example, if user <literal>joe</> has password <literal>xyzzy</>,
+       <productname>PostgreSQL</> will store the md5 hash of
+       <literal>xyzzyjoe</>.  A password that does not follow that
+       format is assumed to be unencrypted.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>rolvaliduntil</structfield></entry>
+      <entry><type>timestamptz</type></entry>
+      <entry>Password expiry time (only used for password authentication);
+       null if no expiration</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+
+  <table id="catalog-rolattr-bitmap-table">
+   <title><structfield>rolattr</> bitmap positions</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Position</entry>
+      <entry>Attribute</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><literal>0</literal></entry>
+      <entry>Superuser</entry>
       <entry>Role has superuser privileges</entry>
      </row>
 
      <row>
-      <entry><structfield>rolinherit</structfield></entry>
-      <entry><type>bool</type></entry>
-      <entry>Role automatically inherits privileges of roles it is a
-       member of</entry>
+      <entry><literal>1</literal></entry>
+      <entry>Inherit</entry>
+      <entry>Role automatically inherits privileges of roles it is a member of</entry>
      </row>
 
      <row>
-      <entry><structfield>rolcreaterole</structfield></entry>
-      <entry><type>bool</type></entry>
+      <entry><literal>2</literal></entry>
+      <entry>Create Role</entry>
       <entry>Role can create more roles</entry>
      </row>
 
      <row>
-      <entry><structfield>rolcreatedb</structfield></entry>
-      <entry><type>bool</type></entry>
+      <entry><literal>3</literal></entry>
+      <entry>Create DB</entry>
       <entry>Role can create databases</entry>
      </row>
 
      <row>
-      <entry><structfield>rolcatupdate</structfield></entry>
-      <entry><type>bool</type></entry>
+      <entry><literal>4</literal></entry>
+      <entry>Catalog Update</entry>
       <entry>
-       Role can update system catalogs directly.  (Even a superuser cannot do
-       this unless this column is true)
+       Role can update system catalogs directly.  (Even a superuser cannot do this unless this column is true)
       </entry>
      </row>
 
      <row>
-      <entry><structfield>rolcanlogin</structfield></entry>
-      <entry><type>bool</type></entry>
+      <entry><literal>5</literal></entry>
+      <entry>Can Login</entry>
       <entry>
-       Role can log in. That is, this role can be given as the initial
-       session authorization identifier
+       Role can log in. That is, this role can be given as the initial session authorization identifier
       </entry>
      </row>
 
      <row>
-      <entry><structfield>rolreplication</structfield></entry>
-      <entry><type>bool</type></entry>
+      <entry><literal>6</literal></entry>
+      <entry>Replication</entry>
       <entry>
        Role is a replication role. That is, this role can initiate streaming
        replication (see <xref linkend="streaming-replication">) and set/unset
@@ -1445,35 +1497,13 @@
      </row>
 
      <row>
-      <entry><structfield>rolconnlimit</structfield></entry>
-      <entry><type>int4</type></entry>
-      <entry>
-       For roles that can log in, this sets maximum number of concurrent
-       connections this role can make.  -1 means no limit.
-      </entry>
-     </row>
-
-     <row>
-      <entry><structfield>rolpassword</structfield></entry>
-      <entry><type>text</type></entry>
+      <entry><literal>7</literal></entry>
+      <entry>By-pass Row Level Security</entry>
       <entry>
-       Password (possibly encrypted); null if none.  If the password
-       is encrypted, this column will begin with the string <literal>md5</>
-       followed by a 32-character hexadecimal MD5 hash.  The MD5 hash
-       will be of the user's password concatenated to their user name.
-       For example, if user <literal>joe</> has password <literal>xyzzy</>,
-       <productname>PostgreSQL</> will store the md5 hash of
-       <literal>xyzzyjoe</>.  A password that does not follow that
-       format is assumed to be unencrypted.
+       Role can by-pass row level security policies when <literal>row_security</> is set <literal>off</>
       </entry>
      </row>
 
-     <row>
-      <entry><structfield>rolvaliduntil</structfield></entry>
-      <entry><type>timestamptz</type></entry>
-      <entry>Password expiry time (only used for password authentication);
-       null if no expiration</entry>
-     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7934a12..20b8325 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -15139,6 +15139,123 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
     are immediately available without doing <command>SET ROLE</>.
    </para>
 
+   <para>
+    <xref linkend="functions-info-role-attribute-table"> lists functions that
+    allow the user to query role attribute information programmatically.
+   </para>
+
+   <table id="functions-info-role-attribute-table">
+    <title>Role Attribute Inquiry Functions</title>
+    <tgroup cols="3">
+     <thead>
+      <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry></row>
+     </thead>
+     <tbody>
+      <row>
+       <entry><literal><function>pg_has_role_attribute(role, attribute)</function></literal></entry>
+       <entry><type>boolean</type></entry>
+       <entry>does role have the permissions allowed by named attribute</entry>
+      </row>
+      <row>
+       <entry><literal><function>pg_check_role_attribute(role, attribute)</function></literal></entry>
+       <entry><type>boolean</type></entry>
+       <entry>does role have the named attribute</entry>
+      </row>
+      <row>
+       <entry><literal><function>pg_check_role_attribute(role_attributes, attribute)</function></literal></entry>
+       <entry><type>boolean</type></entry>
+       <entry>is attribute set in bitmap of role attributes</entry>
+      </row>
+      <row>
+       <entry><literal><function>pg_all_role_attributes(role_attributes)</function></literal></entry>
+       <entry><type>boolean</type></entry>
+       <entry>convert bitmap of role attribute representation to string array</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
+   <para>
+    <function>pg_has_role_attribute</function> checks the attribute permissions
+    given to a role.  It will always return <literal>true</literal> for roles
+    with superuser privileges unless the attribute being checked is
+    <literal>CATUPDATE</literal> (superuser cannot bypass
+    <literal>CATUPDATE</literal> permissions). The role can be specified by name
+    and by OID. The attribute is specified by a text string which must evaluate
+    to one of the following role attributes:
+    <literal>SUPERUSER</literal>,
+    <literal>INHERIT</literal>,
+    <literal>CREATEROLE</literal>,
+    <literal>CREATEDB</literal>,
+    <literal>CATUPDATE</literal>,
+    <literal>CANLOGIN</literal>,
+    <literal>REPLICATION</literal>, or
+    <literal>BYPASSRLS</literal>.
+    Example:
+<programlisting>
+SELECT pg_has_role_attribute('joe', 'SUPERUSER');
+ pg_has_role_attribute 
+-----------------------
+ f
+(1 row)
+
+SELECT rolname, pg_has_role_attribute(oid, 'INHERIT') AS rolinherit FROM pg_roles;
+ rolname  | rolinherit 
+----------+------------
+ postgres | t
+ joe      | t
+(2 rows)
+</programlisting>
+   </para>
+
+   <para>
+    <function>pg_check_role_attribute</function> check the attribute value given
+    to a role.  The role can be specified by name and by OID.  The attribute is
+    specified by a text string which must evaluate to a valid role attribute (see
+    <function>pg_has_role_attribute</function>).  A third variant of this function
+    allows for a bitmap representation (<literal>bigint</literal>) of attributes
+    to be given instead of a role.
+    Example:
+<programlisting>
+SELECT pg_check_role_attribute('joe', 'SUPERUSER');
+ pg_check_role_attribute 
+-------------------------
+ f
+(1 row)
+
+SELECT rolname, pg_check_role_attribute(oid, 'INHERIT') as rolinherit FROM pg_roles;
+ rolname  | rolinherit 
+----------+------------
+ postgres | t
+ joe      | t
+(2 rows)
+ t
+(1 row)
+
+
+SELECT rolname, pg_check_role_attribute(rolattr, 'SUPERUSER') AS rolsuper FROM pg_authid;
+ rolname  | rolsuper 
+----------+----------
+ postgres | t
+ joe      | f
+(2 rows)
+</programlisting>
+   </para>
+
+   <para>
+    <function>pg_all_role_attributes</function> convert a set of role attributes
+    represented by an <literal>bigint</literal> bitmap to a string array.
+    Example:
+<programlisting>
+SELECT rolname, pg_all_role_attributes(rolattr) AS attributes FROM pg_authid;
+ rolname  |                                          attributes                                           
+----------+-----------------------------------------------------------------------------------------------
+ postgres | {Superuser,Inherit,"Create Role","Create DB","Catalog Update",Login,Replication,"Bypass RLS"}
+ joe      | {Inherit,Login}
+(2 rows)
+</programlisting>
+   </para>
+
   <para>
    <xref linkend="functions-info-schema-table"> shows functions that
    determine whether a certain object is <firstterm>visible</> in the
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 133143d..3181a79 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -27,6 +27,7 @@
 #include "miscadmin.h"
 #include "replication/walreceiver.h"
 #include "storage/smgr.h"
+#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/numeric.h"
 #include "utils/guc.h"
@@ -54,7 +55,7 @@ pg_start_backup(PG_FUNCTION_ARGS)
 
 	backupidstr = text_to_cstring(backupid);
 
-	if (!superuser() && !has_rolreplication(GetUserId()))
+	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 		   errmsg("must be superuser or replication role to run a backup")));
@@ -82,7 +83,7 @@ pg_stop_backup(PG_FUNCTION_ARGS)
 {
 	XLogRecPtr	stoppoint;
 
-	if (!superuser() && !has_rolreplication(GetUserId()))
+	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 		 (errmsg("must be superuser or replication role to run a backup"))));
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a403c64..a6de2ff 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -28,7 +28,7 @@ all: $(BKIFILES) schemapg.h
 # indexing.h had better be last, and toasting.h just before it.
 
 POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
-	pg_proc.h pg_type.h pg_attribute.h pg_class.h \
+	acldefs.h pg_proc.h pg_type.h pg_attribute.h pg_class.h \
 	pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
 	pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
 	pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d30612c..21d282c 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3423,26 +3423,6 @@ aclcheck_error_type(AclResult aclerr, Oid typeOid)
 }
 
 
-/* Check if given user has rolcatupdate privilege according to pg_authid */
-static bool
-has_rolcatupdate(Oid roleid)
-{
-	bool		rolcatupdate;
-	HeapTuple	tuple;
-
-	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
-	if (!HeapTupleIsValid(tuple))
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_OBJECT),
-				 errmsg("role with OID %u does not exist", roleid)));
-
-	rolcatupdate = ((Form_pg_authid) GETSTRUCT(tuple))->rolcatupdate;
-
-	ReleaseSysCache(tuple);
-
-	return rolcatupdate;
-}
-
 /*
  * Relay for the various pg_*_mask routines depending on object kind
  */
@@ -3630,7 +3610,7 @@ pg_class_aclmask(Oid table_oid, Oid roleid,
 	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
 		IsSystemClass(table_oid, classForm) &&
 		classForm->relkind != RELKIND_VIEW &&
-		!has_rolcatupdate(roleid) &&
+		!has_role_attribute(roleid, ROLE_ATTR_CATUPDATE) &&
 		!allowSystemTableMods)
 	{
 #ifdef ACLDEBUG
@@ -5051,52 +5031,80 @@ pg_extension_ownercheck(Oid ext_oid, Oid roleid)
 }
 
 /*
- * Check whether specified role has CREATEROLE privilege (or is a superuser)
+ * has_role_attribute
+ *   Check if the role with the specified id has been assigned a specific role
+ *   attribute.
+ *
+ * roleid - the oid of the role to check.
+ * attribute - the attribute to check.
+ *
+ * Note: Use this function for role attribute permission checking as it
+ * accounts for superuser status.  It will always return true for roles with
+ * superuser privileges unless the attribute being checked is CATUPDATE
+ * (superusers are not allowed to bypass CATUPDATE permissions).
  *
- * Note: roles do not have owners per se; instead we use this test in
- * places where an ownership-like permissions test is needed for a role.
- * Be sure to apply it to the role trying to do the operation, not the
- * role being operated on!	Also note that this generally should not be
- * considered enough privilege if the target role is a superuser.
- * (We don't handle that consideration here because we want to give a
- * separate error message for such cases, so the caller has to deal with it.)
+ * Note: roles do not have owners per se; instead we use this test in places
+ * where an ownership-like permissions test is needed for a role.  Be sure to
+ * apply it to the role trying to do the operation, not the role being operated
+ * on!  Also note that this generally should not be considered enough privilege
+ * if the target role is a superuser.  (We don't handle that consideration here
+ * because we want to give a separate error message for such cases, so the
+ * caller has to deal with it.)
  */
 bool
-has_createrole_privilege(Oid roleid)
+has_role_attribute(Oid roleid, RoleAttr attribute)
 {
-	bool		result = false;
-	HeapTuple	utup;
-
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
+	/*
+	 * Superusers bypass all permission checking except in the case of CATUPDATE.
+	 */
+	if (!(attribute & ROLE_ATTR_CATUPDATE) && superuser_arg(roleid))
 		return true;
 
-	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
-	if (HeapTupleIsValid(utup))
-	{
-		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole;
-		ReleaseSysCache(utup);
-	}
-	return result;
+	return check_role_attribute(roleid, attribute);
+}
+
+/*
+ * have_role_attribute
+ *   Convenience function for checking if the role id returned by GetUserId() has
+ *   been assigned a specific role attribute.
+ *
+ * attribute - the attribute to check.
+ */
+bool
+have_role_attribute(RoleAttr attribute)
+{
+	return has_role_attribute(GetUserId(), attribute);
 }
 
+/*
+ * check_role_attribute
+ *   Check if the role with the specified id has been assigned a specific role
+ *   attribute.
+ *
+ * roleid - the oid of the role to check.
+ * attribute - the attribute to check.
+ *
+ * Note: This function should only be used for checking the value of an individual
+ * attribute in the rolattr bitmap and should *not* be used for permission checking.
+ * For the purposes of permission checking use 'has_role_attribute' instead.
+ */
 bool
-has_bypassrls_privilege(Oid roleid)
+check_role_attribute(Oid roleid, RoleAttr attribute)
 {
-	bool		result = false;
-	HeapTuple	utup;
+	RoleAttr	attributes;
+	HeapTuple	tuple;
 
-	/* Superusers bypass all permission checking. */
-	if (superuser_arg(roleid))
-		return true;
+	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
 
-	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
-	if (HeapTupleIsValid(utup))
-	{
-		result = ((Form_pg_authid) GETSTRUCT(utup))->rolbypassrls;
-		ReleaseSysCache(utup);
-	}
-	return result;
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("role with OID %u does not exist", roleid)));
+
+	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
+	ReleaseSysCache(tuple);
+
+	return (attributes & attribute);
 }
 
 /*
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index ca89879..415ac17 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -90,6 +90,8 @@ my $BOOTSTRAP_SUPERUSERID =
   find_defined_symbol('pg_authid.h', 'BOOTSTRAP_SUPERUSERID');
 my $PG_CATALOG_NAMESPACE =
   find_defined_symbol('pg_namespace.h', 'PG_CATALOG_NAMESPACE');
+my $ROLE_ATTR_ALL =
+  find_defined_symbol('acldefs.h', 'ROLE_ATTR_ALL');
 
 # Read all the input header files into internal data structures
 my $catalogs = Catalog::Catalogs(@input_files);
@@ -144,6 +146,7 @@ foreach my $catname (@{ $catalogs->{names} })
 			# substitute constant values we acquired above
 			$row->{bki_values} =~ s/\bPGUID\b/$BOOTSTRAP_SUPERUSERID/g;
 			$row->{bki_values} =~ s/\bPGNSP\b/$PG_CATALOG_NAMESPACE/g;
+			$row->{bki_values} =~ s/\bPGROLATTRALL/$ROLE_ATTR_ALL/g;
 
 			# Save pg_type info for pg_attribute processing below
 			if ($catname eq 'pg_type')
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index a036c62..87b6d8c 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -2884,7 +2884,12 @@ CREATE VIEW user_mapping_options AS
            CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
            CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                        OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
-                       OR (SELECT rolsuper FROM pg_authid WHERE rolname = current_user) THEN (pg_options_to_table(um.umoptions)).option_value
+                       OR (
+                            SELECT pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER') AS rolsuper
+                            FROM pg_authid
+                            WHERE rolname = current_user
+                          )
+                       THEN (pg_options_to_table(um.umoptions)).option_value
                      ELSE NULL END AS character_data) AS option_value
     FROM _pg_user_mappings um;
 
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index e261307..6df7e4d 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1310,7 +1310,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			}
 			else
 			{
-				if (!has_createrole_privilege(roleid))
+				if (!has_role_attribute(roleid, ROLE_ATTR_CREATEROLE))
 					ereport(ERROR,
 							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 							 errmsg("must have CREATEROLE privilege")));
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 22b8cee..ae93832 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -9,17 +9,17 @@
 CREATE VIEW pg_roles AS
     SELECT
         rolname,
-        rolsuper,
-        rolinherit,
-        rolcreaterole,
-        rolcreatedb,
-        rolcatupdate,
-        rolcanlogin,
-        rolreplication,
+        pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER') AS rolsuper,
+        pg_check_role_attribute(pg_authid.rolattr, 'INHERIT') AS rolinherit,
+        pg_check_role_attribute(pg_authid.rolattr, 'CREATEROLE') AS rolcreaterole,
+        pg_check_role_attribute(pg_authid.rolattr, 'CREATEDB') AS rolcreatedb,
+        pg_check_role_attribute(pg_authid.rolattr, 'CATUPDATE') AS rolcatupdate,
+        pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN') AS rolcanlogin,
+        pg_check_role_attribute(pg_authid.rolattr, 'REPLICATION') AS rolreplication,
+        pg_check_role_attribute(pg_authid.rolattr, 'BYPASSRLS') AS rolbypassrls,
         rolconnlimit,
         '********'::text as rolpassword,
         rolvaliduntil,
-        rolbypassrls,
         setconfig as rolconfig,
         pg_authid.oid
     FROM pg_authid LEFT JOIN pg_db_role_setting s
@@ -29,16 +29,16 @@ CREATE VIEW pg_shadow AS
     SELECT
         rolname AS usename,
         pg_authid.oid AS usesysid,
-        rolcreatedb AS usecreatedb,
-        rolsuper AS usesuper,
-        rolcatupdate AS usecatupd,
-        rolreplication AS userepl,
+        pg_check_role_attribute(pg_authid.rolattr, 'CREATEDB') AS usecreatedb,
+        pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER') AS usesuper,
+        pg_check_role_attribute(pg_authid.rolattr, 'CATUPDATE') AS usecatupd,
+        pg_check_role_attribute(pg_authid.rolattr, 'REPLICATION') AS userepl,
         rolpassword AS passwd,
         rolvaliduntil::abstime AS valuntil,
         setconfig AS useconfig
     FROM pg_authid LEFT JOIN pg_db_role_setting s
     ON (pg_authid.oid = setrole AND setdatabase = 0)
-    WHERE rolcanlogin;
+    WHERE pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN');
 
 REVOKE ALL on pg_shadow FROM public;
 
@@ -48,7 +48,7 @@ CREATE VIEW pg_group AS
         oid AS grosysid,
         ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
     FROM pg_authid
-    WHERE NOT rolcanlogin;
+    WHERE NOT pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN');
 
 CREATE VIEW pg_user AS
     SELECT
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 1a5244c..c079168 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -85,7 +85,6 @@ static bool get_db_info(const char *name, LOCKMODE lockmode,
 			Oid *dbLastSysOidP, TransactionId *dbFrozenXidP,
 			MultiXactId *dbMinMultiP,
 			Oid *dbTablespace, char **dbCollate, char **dbCtype);
-static bool have_createdb_privilege(void);
 static void remove_dbtablespaces(Oid db_id);
 static bool check_db_file_conflict(Oid db_id);
 static int	errdetail_busy_db(int notherbackends, int npreparedxacts);
@@ -291,7 +290,7 @@ createdb(const CreatedbStmt *stmt)
 	 * "giveaway" attacks.  Note that a superuser will always have both of
 	 * these privileges a fortiori.
 	 */
-	if (!have_createdb_privilege())
+	if (!have_role_attribute(ROLE_ATTR_CREATEDB))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied to create database")));
@@ -965,7 +964,7 @@ RenameDatabase(const char *oldname, const char *newname)
 					   oldname);
 
 	/* must have createdb rights */
-	if (!have_createdb_privilege())
+	if (!have_role_attribute(ROLE_ATTR_CREATEDB))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied to rename database")));
@@ -1623,7 +1622,7 @@ AlterDatabaseOwner(const char *dbname, Oid newOwnerId)
 		 * databases.  Because superusers will always have this right, we need
 		 * no special case for them.
 		 */
-		if (!have_createdb_privilege())
+		if (!have_role_attribute(ROLE_ATTR_CREATEDB))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				   errmsg("permission denied to change owner of database")));
@@ -1802,26 +1801,6 @@ get_db_info(const char *name, LOCKMODE lockmode,
 	return result;
 }
 
-/* Check if current user has createdb privileges */
-static bool
-have_createdb_privilege(void)
-{
-	bool		result = false;
-	HeapTuple	utup;
-
-	/* Superusers can always do everything */
-	if (superuser())
-		return true;
-
-	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId()));
-	if (HeapTupleIsValid(utup))
-	{
-		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreatedb;
-		ReleaseSysCache(utup);
-	}
-	return result;
-}
-
 /*
  * Remove tablespace directories
  *
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 1a73fd8..564f77a 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -56,14 +56,6 @@ static void DelRoleMems(const char *rolename, Oid roleid,
 			bool admin_opt);
 
 
-/* Check if current user has createrole privileges */
-static bool
-have_createrole_privilege(void)
-{
-	return has_createrole_privilege(GetUserId());
-}
-
-
 /*
  * CREATE ROLE
  */
@@ -81,13 +73,7 @@ CreateRole(CreateRoleStmt *stmt)
 	char	   *password = NULL;	/* user password */
 	bool		encrypt_password = Password_encryption; /* encrypt password? */
 	char		encrypted_password[MD5_PASSWD_LEN + 1];
-	bool		issuper = false;	/* Make the user a superuser? */
-	bool		inherit = true; /* Auto inherit privileges? */
-	bool		createrole = false;		/* Can this user create roles? */
-	bool		createdb = false;		/* Can the user create databases? */
-	bool		canlogin = false;		/* Can this user login? */
-	bool		isreplication = false;	/* Is this a replication role? */
-	bool		bypassrls = false;		/* Is this a row security enabled role? */
+	RoleAttr	attributes;
 	int			connlimit = -1; /* maximum connections allowed */
 	List	   *addroleto = NIL;	/* roles to make this a member of */
 	List	   *rolemembers = NIL;		/* roles to be members of this role */
@@ -109,13 +95,17 @@ CreateRole(CreateRoleStmt *stmt)
 	DefElem    *dvalidUntil = NULL;
 	DefElem    *dbypassRLS = NULL;
 
-	/* The defaults can vary depending on the original statement type */
+	/*
+	 * Every role has INHERIT by default, and CANLOGIN depends on the statement
+	 * type.
+	 */
+	attributes = ROLE_ATTR_INHERIT;
 	switch (stmt->stmt_type)
 	{
 		case ROLESTMT_ROLE:
 			break;
 		case ROLESTMT_USER:
-			canlogin = true;
+			attributes |= ROLE_ATTR_CANLOGIN;
 			/* may eventually want inherit to default to false here */
 			break;
 		case ROLESTMT_GROUP:
@@ -249,18 +239,76 @@ CreateRole(CreateRoleStmt *stmt)
 
 	if (dpassword && dpassword->arg)
 		password = strVal(dpassword->arg);
+
+	/* Set up role attributes and check permissions to set each of them */
 	if (dissuper)
-		issuper = intVal(dissuper->arg) != 0;
+	{
+		if (intVal(dissuper->arg) != 0)
+		{
+			if (!superuser())
+				ereport(ERROR,
+						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						 errmsg("must be superuser to create superusers")));
+			attributes |= ROLE_ATTR_SUPERUSER;
+		}
+		else
+			attributes &= ~ROLE_ATTR_SUPERUSER;
+	}
 	if (dinherit)
-		inherit = intVal(dinherit->arg) != 0;
+	{
+		if (intVal(dinherit->arg) != 0)
+			attributes |= ROLE_ATTR_INHERIT;
+		else
+			attributes &= ~ROLE_ATTR_INHERIT;
+	}
 	if (dcreaterole)
-		createrole = intVal(dcreaterole->arg) != 0;
+	{
+		if (intVal(dcreaterole->arg) != 0)
+			attributes |= ROLE_ATTR_CREATEROLE;
+		else
+			attributes &= ~ROLE_ATTR_CREATEROLE;
+	}
 	if (dcreatedb)
-		createdb = intVal(dcreatedb->arg) != 0;
+	{
+		if (intVal(dcreatedb->arg) != 0)
+			attributes |= ROLE_ATTR_CREATEDB;
+		else
+			attributes &= ~ROLE_ATTR_CREATEDB;
+	}
 	if (dcanlogin)
-		canlogin = intVal(dcanlogin->arg) != 0;
+	{
+		if (intVal(dcanlogin->arg) != 0)
+			attributes |= ROLE_ATTR_CANLOGIN;
+		else
+			attributes &= ~ROLE_ATTR_CANLOGIN;
+	}
 	if (disreplication)
-		isreplication = intVal(disreplication->arg) != 0;
+	{
+		if (intVal(disreplication->arg) != 0)
+		{
+			if (!superuser())
+				ereport(ERROR,
+						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						 errmsg("must be superuser to create replication users")));
+			attributes |= ROLE_ATTR_REPLICATION;
+		}
+		else
+			attributes &= ~ROLE_ATTR_REPLICATION;
+	}
+	if (dbypassRLS)
+	{
+		if (intVal(dbypassRLS->arg) != 0)
+		{
+			if (!superuser())
+				ereport(ERROR,
+						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						 errmsg("must be superuser to change bypassrls attribute")));
+			attributes |= ROLE_ATTR_BYPASSRLS;
+		}
+		else
+			attributes &= ~ROLE_ATTR_BYPASSRLS;
+	}
+
 	if (dconnlimit)
 	{
 		connlimit = intVal(dconnlimit->arg);
@@ -277,38 +325,12 @@ CreateRole(CreateRoleStmt *stmt)
 		adminmembers = (List *) dadminmembers->arg;
 	if (dvalidUntil)
 		validUntil = strVal(dvalidUntil->arg);
-	if (dbypassRLS)
-		bypassrls = intVal(dbypassRLS->arg) != 0;
 
-	/* Check some permissions first */
-	if (issuper)
-	{
-		if (!superuser())
-			ereport(ERROR,
-					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to create superusers")));
-	}
-	else if (isreplication)
-	{
-		if (!superuser())
-			ereport(ERROR,
-					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				   errmsg("must be superuser to create replication users")));
-	}
-	else if (bypassrls)
-	{
-		if (!superuser())
-			ereport(ERROR,
-					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to change bypassrls attribute.")));
-	}
-	else
-	{
-		if (!have_createrole_privilege())
-			ereport(ERROR,
-					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("permission denied to create role")));
-	}
+	/* Check permissions */
+	if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied to create role")));
 
 	if (strcmp(stmt->role, "public") == 0 ||
 		strcmp(stmt->role, "none") == 0)
@@ -364,14 +386,8 @@ CreateRole(CreateRoleStmt *stmt)
 	new_record[Anum_pg_authid_rolname - 1] =
 		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
 
-	new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
-	new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
-	new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
-	new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
-	/* superuser gets catupdate right by default */
-	new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper);
-	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
-	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
+	new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
+
 	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
 
 	if (password)
@@ -394,8 +410,6 @@ CreateRole(CreateRoleStmt *stmt)
 	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
 
-	new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls);
-
 	tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
 
 	/*
@@ -508,6 +522,7 @@ AlterRole(AlterRoleStmt *stmt)
 	DefElem    *dvalidUntil = NULL;
 	DefElem    *dbypassRLS = NULL;
 	Oid			roleid;
+	RoleAttr	attributes;
 
 	/* Extract options from the statement node tree */
 	foreach(option, stmt->options)
@@ -658,31 +673,34 @@ AlterRole(AlterRoleStmt *stmt)
 	roleid = HeapTupleGetOid(tuple);
 
 	/*
-	 * To mess with a superuser you gotta be superuser; else you need
-	 * createrole, or just want to change your own password
+	 * To mess with a superuser or a replication user you gotta be superuser;
+	 * else you need createrole, or just want to change your own password
 	 */
-	if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper || issuper >= 0)
+
+	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
+
+	if ((attributes & ROLE_ATTR_SUPERUSER) || issuper >= 0)
 	{
 		if (!superuser())
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("must be superuser to alter superusers")));
 	}
-	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolreplication || isreplication >= 0)
+	else if ((attributes & ROLE_ATTR_REPLICATION) || isreplication >= 0)
 	{
 		if (!superuser())
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("must be superuser to alter replication users")));
 	}
-	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolbypassrls || bypassrls >= 0)
+	else if ((attributes & ROLE_ATTR_BYPASSRLS) || bypassrls >= 0)
 	{
 		if (!superuser())
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("must be superuser to change bypassrls attribute")));
 	}
-	else if (!have_createrole_privilege())
+	else if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
 	{
 		if (!(inherit < 0 &&
 			  createrole < 0 &&
@@ -743,43 +761,71 @@ AlterRole(AlterRoleStmt *stmt)
 	 */
 	if (issuper >= 0)
 	{
-		new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);
-		new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
-
-		new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);
-		new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true;
+		if (issuper > 0)
+			attributes |= ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE;
+		else
+			attributes &= ~(ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE);
+		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
 	if (inherit >= 0)
 	{
-		new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
-		new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
+		if (inherit > 0)
+			attributes |= ROLE_ATTR_INHERIT;
+		else
+			attributes &= ~ROLE_ATTR_INHERIT;
+		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
 	if (createrole >= 0)
 	{
-		new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);
-		new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
+		if (createrole > 0)
+			attributes |= ROLE_ATTR_CREATEROLE;
+		else
+			attributes &= ~ROLE_ATTR_CREATEROLE;
+		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
 	if (createdb >= 0)
 	{
-		new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0);
-		new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true;
+		if (createdb > 0)
+			attributes |= ROLE_ATTR_CREATEDB;
+		else
+			attributes &= ~ROLE_ATTR_CREATEDB;
+		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
 	if (canlogin >= 0)
 	{
-		new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0);
-		new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true;
+		if (canlogin > 0)
+			attributes |= ROLE_ATTR_CANLOGIN;
+		else
+			attributes &= ~ROLE_ATTR_CANLOGIN;
+		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
 	if (isreplication >= 0)
 	{
-		new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication > 0);
-		new_record_repl[Anum_pg_authid_rolreplication - 1] = true;
+		if (isreplication > 0)
+			attributes |= ROLE_ATTR_REPLICATION;
+		else
+			attributes &= ~ROLE_ATTR_REPLICATION;
+		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
 	}
 
+	if (bypassrls >= 0)
+	{
+		if (bypassrls > 0)
+			attributes |= ROLE_ATTR_BYPASSRLS;
+		else
+			attributes &= ~ROLE_ATTR_BYPASSRLS;
+		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
+	}
+
+	/* If any role attributes were set, then update. */
+	if (new_record_repl[Anum_pg_authid_rolattr - 1])
+		new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
+
 	if (dconnlimit)
 	{
 		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
@@ -815,11 +861,6 @@ AlterRole(AlterRoleStmt *stmt)
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
 	new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
 
-	if (bypassrls >= 0)
-	{
-		new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls > 0);
-		new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
-	}
 
 	new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
 								  new_record_nulls, new_record_repl);
@@ -867,6 +908,7 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
 	HeapTuple	roletuple;
 	Oid			databaseid = InvalidOid;
 	Oid			roleid = InvalidOid;
+	RoleAttr	attributes;
 
 	if (stmt->role)
 	{
@@ -889,7 +931,8 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
 		 * To mess with a superuser you gotta be superuser; else you need
 		 * createrole, or just want to change your own settings
 		 */
-		if (((Form_pg_authid) GETSTRUCT(roletuple))->rolsuper)
+		attributes = ((Form_pg_authid) GETSTRUCT(roletuple))->rolattr;
+		if (attributes & ROLE_ATTR_SUPERUSER)
 		{
 			if (!superuser())
 				ereport(ERROR,
@@ -898,7 +941,7 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
 		}
 		else
 		{
-			if (!have_createrole_privilege() &&
+			if (!have_role_attribute(ROLE_ATTR_CREATEROLE) &&
 				HeapTupleGetOid(roletuple) != GetUserId())
 				ereport(ERROR,
 						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -951,7 +994,7 @@ DropRole(DropRoleStmt *stmt)
 				pg_auth_members_rel;
 	ListCell   *item;
 
-	if (!have_createrole_privilege())
+	if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied to drop role")));
@@ -973,6 +1016,7 @@ DropRole(DropRoleStmt *stmt)
 		char	   *detail_log;
 		SysScanDesc sscan;
 		Oid			roleid;
+		RoleAttr	attributes;
 
 		tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
 		if (!HeapTupleIsValid(tuple))
@@ -1013,8 +1057,8 @@ DropRole(DropRoleStmt *stmt)
 		 * roles but not superuser roles.  This is mainly to avoid the
 		 * scenario where you accidentally drop the last superuser.
 		 */
-		if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper &&
-			!superuser())
+		attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
+		if ((attributes & ROLE_ATTR_SUPERUSER) && !superuser())
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("must be superuser to drop superusers")));
@@ -1128,6 +1172,7 @@ RenameRole(const char *oldname, const char *newname)
 	bool		repl_repl[Natts_pg_authid];
 	int			i;
 	Oid			roleid;
+	RoleAttr	attributes;
 
 	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
 	dsc = RelationGetDescr(rel);
@@ -1173,7 +1218,8 @@ RenameRole(const char *oldname, const char *newname)
 	/*
 	 * createrole is enough privilege unless you want to mess with a superuser
 	 */
-	if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
+	attributes = ((Form_pg_authid) GETSTRUCT(oldtuple))->rolattr;
+	if (attributes & ROLE_ATTR_SUPERUSER)
 	{
 		if (!superuser())
 			ereport(ERROR,
@@ -1182,7 +1228,7 @@ RenameRole(const char *oldname, const char *newname)
 	}
 	else
 	{
-		if (!have_createrole_privilege())
+		if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("permission denied to rename role")));
@@ -1409,7 +1455,7 @@ AddRoleMems(const char *rolename, Oid roleid,
 	}
 	else
 	{
-		if (!have_createrole_privilege() &&
+		if (!have_role_attribute(ROLE_ATTR_CREATEROLE) &&
 			!is_admin_of_role(grantorId, roleid))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -1555,7 +1601,7 @@ DelRoleMems(const char *rolename, Oid roleid,
 	}
 	else
 	{
-		if (!have_createrole_privilege() &&
+		if (!have_role_attribute(ROLE_ATTR_CREATEROLE) &&
 			!is_admin_of_role(GetUserId(), roleid))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 6ce8dae..491dc38 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -776,6 +776,7 @@ check_session_authorization(char **newval, void **extra, GucSource source)
 	Oid			roleid;
 	bool		is_superuser;
 	role_auth_extra *myextra;
+	RoleAttr	attributes;
 
 	/* Do nothing for the boot_val default of NULL */
 	if (*newval == NULL)
@@ -800,7 +801,8 @@ check_session_authorization(char **newval, void **extra, GucSource source)
 	}
 
 	roleid = HeapTupleGetOid(roleTup);
-	is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
+	attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
+	is_superuser = (attributes & ROLE_ATTR_SUPERUSER);
 
 	ReleaseSysCache(roleTup);
 
@@ -844,6 +846,7 @@ check_role(char **newval, void **extra, GucSource source)
 	Oid			roleid;
 	bool		is_superuser;
 	role_auth_extra *myextra;
+	RoleAttr	attributes;
 
 	if (strcmp(*newval, "none") == 0)
 	{
@@ -872,7 +875,8 @@ check_role(char **newval, void **extra, GucSource source)
 		}
 
 		roleid = HeapTupleGetOid(roleTup);
-		is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
+		attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
+		is_superuser = (attributes & ROLE_ATTR_SUPERUSER);
 
 		ReleaseSysCache(roleTup);
 
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1977f09..1a38f56 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -17,18 +17,14 @@
 
 #include <unistd.h>
 
+#include "access/xlog_internal.h"
+#include "catalog/pg_type.h"
 #include "fmgr.h"
 #include "funcapi.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
-
-#include "access/xlog_internal.h"
-
-#include "catalog/pg_type.h"
-
 #include "nodes/makefuncs.h"
-
-#include "mb/pg_wchar.h"
-
+#include "utils/acl.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
@@ -36,11 +32,9 @@
 #include "utils/pg_lsn.h"
 #include "utils/resowner.h"
 #include "utils/lsyscache.h"
-
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/logicalfuncs.h"
-
 #include "storage/fd.h"
 
 /* private date for writing out data */
@@ -205,7 +199,7 @@ XLogRead(char *buf, TimeLineID tli, XLogRecPtr startptr, Size count)
 static void
 check_permissions(void)
 {
-	if (!superuser() && !has_rolreplication(GetUserId()))
+	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 (errmsg("must be superuser or replication role to use replication slots"))));
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index bd4701f..c113a0b 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -20,13 +20,14 @@
 #include "replication/slot.h"
 #include "replication/logical.h"
 #include "replication/logicalfuncs.h"
+#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/pg_lsn.h"
 
 static void
 check_permissions(void)
 {
-	if (!superuser() && !has_rolreplication(GetUserId()))
+	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 (errmsg("must be superuser or replication role to use replication slots"))));
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index 6c232dc..f41ad34 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -521,7 +521,7 @@ check_enable_rls(Oid relid, Oid checkAsUser)
 	 */
 	if (!checkAsUser && row_security == ROW_SECURITY_OFF)
 	{
-		if (has_bypassrls_privilege(user_id))
+		if (has_role_attribute(user_id, ROLE_ATTR_BYPASSRLS))
 			/* OK to bypass */
 			return RLS_NONE_ENV;
 		else
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index dc6eb2c..4c03955 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -115,6 +115,7 @@ static Oid	convert_type_name(text *typename);
 static AclMode convert_type_priv_string(text *priv_type_text);
 static AclMode convert_role_priv_string(text *priv_type_text);
 static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
+static RoleAttr convert_role_attr_string(text *attr_type_text);
 
 static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue);
 
@@ -4602,6 +4603,186 @@ pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode)
 	return ACLCHECK_NO_PRIV;
 }
 
+/*
+ * pg_has_role_attribute_id
+ *		Check that the role with the given oid has the given named role
+ *		attribute.
+ *
+ * Note: This function applies superuser checks.  Therefore, if the provided
+ * role is a superuser, then the result will always be true.
+ */
+Datum
+pg_has_role_attribute_id(PG_FUNCTION_ARGS)
+{
+	Oid			roleoid = PG_GETARG_OID(0);
+	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+	RoleAttr	attribute;
+
+	attribute = convert_role_attr_string(attr_type_text);
+
+	PG_RETURN_BOOL(has_role_attribute(roleoid, attribute));
+}
+
+/*
+ * pg_has_role_attribute_name
+ *		Check that the named role has the given named role attribute.
+ *
+ * Note: This function applies superuser checks.  Therefore, if the provided
+ * role is a superuser, then the result will always be true.
+ */
+Datum
+pg_has_role_attribute_name(PG_FUNCTION_ARGS)
+{
+	Name		rolename = PG_GETARG_NAME(0);
+	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+	Oid			roleoid;
+	RoleAttr	attribute;
+
+	roleoid = get_role_oid(NameStr(*rolename), false);
+	attribute = convert_role_attr_string(attr_type_text);
+
+	PG_RETURN_BOOL(has_role_attribute(roleoid, attribute));
+}
+
+/*
+ * pg_check_role_attribute_id
+ *		Check that the role with the given oid has the given named role
+ *		attribute.
+ *
+ * Note: This function is different from 'pg_has_role_attribute_id_attr' in that
+ * it does *not* apply any superuser checks.  Therefore, this function will
+ * always return the set value of the attribute, despite the superuser-ness of
+ * the provided role.
+ */
+Datum
+pg_check_role_attribute_id(PG_FUNCTION_ARGS)
+{
+	Oid			roleoid = PG_GETARG_OID(0);
+	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+	RoleAttr	attribute;
+
+	attribute = convert_role_attr_string(attr_type_text);
+
+	PG_RETURN_BOOL(check_role_attribute(roleoid, attribute));
+}
+
+/*
+ * pg_check_role_attribute_name
+ *		Check that the named role has the given named role attribute.
+ *
+ * Note: This function is different from 'pg_has_role_attribute_name_attr' in
+ * that it does *not* apply any superuser checks.  Therefore, this function will
+ * always return the set value of the attribute, despite the superuser-ness of
+ * the provided role.
+ */
+Datum
+pg_check_role_attribute_name(PG_FUNCTION_ARGS)
+{
+	Name		rolename = PG_GETARG_NAME(0);
+	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+	Oid			roleoid;
+	RoleAttr	attribute;
+
+	roleoid = get_role_oid(NameStr(*rolename), false);
+	attribute = convert_role_attr_string(attr_type_text);
+
+	PG_RETURN_BOOL(check_role_attribute(roleoid, attribute));
+}
+
+/*
+ * pg_check_role_attribute_attrs
+ *		Check that the named attribute is enabled in the given RoleAttr
+ *		representation of role attributes.
+ */
+Datum
+pg_check_role_attribute_attrs(PG_FUNCTION_ARGS)
+{
+	RoleAttr	attributes = PG_GETARG_INT64(0);
+	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+	RoleAttr	attribute;
+
+	attribute = convert_role_attr_string(attr_type_text);
+
+	PG_RETURN_BOOL(attributes & attribute);
+}
+
+/*
+ * pg_all_role_attributes
+ *		Convert a RoleAttr representation of role attributes into an array of
+ *		corresponding text values.
+ *
+ * The first and only argument is a RoleAttr (int64) representation of the
+ * role attributes.
+ */
+Datum
+pg_all_role_attributes(PG_FUNCTION_ARGS)
+{
+	RoleAttr		attributes = PG_GETARG_INT64(0);
+	Datum		   *temp_array;
+	ArrayType	   *result;
+	int				i = 0;
+
+	/*
+	 * Short-circuit the case for no attributes assigned.
+	 */
+	if (attributes == ROLE_ATTR_NONE)
+		PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));
+
+	temp_array = (Datum *) palloc(N_ROLE_ATTRIBUTES * sizeof(Datum));
+
+	/* Determine which attributes are assigned. */
+	if (attributes & ROLE_ATTR_SUPERUSER)
+		temp_array[i++] = CStringGetTextDatum(_("Superuser"));
+	if (attributes & ROLE_ATTR_INHERIT)
+		temp_array[i++] = CStringGetTextDatum(_("Inherit"));
+	if (attributes & ROLE_ATTR_CREATEROLE)
+		temp_array[i++] = CStringGetTextDatum(_("Create Role"));
+	if (attributes & ROLE_ATTR_CREATEDB)
+		temp_array[i++] = CStringGetTextDatum(_("Create DB"));
+	if (attributes & ROLE_ATTR_CATUPDATE)
+		temp_array[i++] = CStringGetTextDatum(_("Catalog Update"));
+	if (attributes & ROLE_ATTR_CANLOGIN)
+		temp_array[i++] = CStringGetTextDatum(_("Login"));
+	if (attributes & ROLE_ATTR_REPLICATION)
+		temp_array[i++] = CStringGetTextDatum(_("Replication"));
+	if (attributes & ROLE_ATTR_BYPASSRLS)
+		temp_array[i++] = CStringGetTextDatum(_("Bypass RLS"));
+
+	result = construct_array(temp_array, i, TEXTOID, -1, false, 'i');
+
+	PG_RETURN_ARRAYTYPE_P(result);
+}
+
+/*
+ * convert_role_attr_string
+ *		Convert text string to RoleAttr value.
+ */
+static RoleAttr
+convert_role_attr_string(text *attr_type_text)
+{
+	char	   *attr_type = text_to_cstring(attr_type_text);
+
+	if (pg_strcasecmp(attr_type, "SUPERUSER") == 0)
+		return ROLE_ATTR_SUPERUSER;
+	else if (pg_strcasecmp(attr_type, "INHERIT") == 0)
+		return ROLE_ATTR_INHERIT;
+	else if (pg_strcasecmp(attr_type, "CREATEROLE") == 0)
+		return ROLE_ATTR_CREATEROLE;
+	else if (pg_strcasecmp(attr_type, "CREATEDB") == 0)
+		return ROLE_ATTR_CREATEDB;
+	else if (pg_strcasecmp(attr_type, "CATUPDATE") == 0)
+		return ROLE_ATTR_CATUPDATE;
+	else if (pg_strcasecmp(attr_type, "CANLOGIN") == 0)
+		return ROLE_ATTR_CANLOGIN;
+	else if (pg_strcasecmp(attr_type, "REPLICATION") == 0)
+		return ROLE_ATTR_REPLICATION;
+	else if (pg_strcasecmp(attr_type, "BYPASSRLS") == 0)
+		return ROLE_ATTR_BYPASSRLS;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized role attribute: \"%s\"", attr_type)));
+}
 
 /*
  * initialization function (called by InitPostgres)
@@ -4634,23 +4815,6 @@ RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
 }
 
 
-/* Check if specified role has rolinherit set */
-static bool
-has_rolinherit(Oid roleid)
-{
-	bool		result = false;
-	HeapTuple	utup;
-
-	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
-	if (HeapTupleIsValid(utup))
-	{
-		result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
-		ReleaseSysCache(utup);
-	}
-	return result;
-}
-
-
 /*
  * Get a list of roles that the specified roleid has the privileges of
  *
@@ -4697,7 +4861,7 @@ roles_has_privs_of(Oid roleid)
 		int			i;
 
 		/* Ignore non-inheriting roles */
-		if (!has_rolinherit(memberid))
+		if (!has_role_attribute(memberid, ROLE_ATTR_INHERIT))
 			continue;
 
 		/* Find roles that memberid is directly a member of */
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 5c75390..ccb1066 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2308,7 +2308,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 * bypassrls right or is the table owner of the table(s) involved which
 	 * have RLS enabled.
 	 */
-	if (!has_bypassrls_privilege(GetUserId()) &&
+	if (!have_role_attribute(ROLE_ATTR_BYPASSRLS) &&
 		((pk_rel->rd_rel->relrowsecurity &&
 		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
 		 (fk_rel->rd_rel->relrowsecurity &&
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 8fccb4c..db2a0fb 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -40,6 +40,7 @@
 #include "storage/pg_shmem.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
@@ -329,24 +330,6 @@ SetUserIdAndContext(Oid userid, bool sec_def_context)
 
 
 /*
- * Check whether specified role has explicit REPLICATION privilege
- */
-bool
-has_rolreplication(Oid roleid)
-{
-	bool		result = false;
-	HeapTuple	utup;
-
-	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
-	if (HeapTupleIsValid(utup))
-	{
-		result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication;
-		ReleaseSysCache(utup);
-	}
-	return result;
-}
-
-/*
  * Initialize user identity during normal backend startup
  */
 void
@@ -375,7 +358,7 @@ InitializeSessionUserId(const char *rolename)
 	roleid = HeapTupleGetOid(roleTup);
 
 	AuthenticatedUserId = roleid;
-	AuthenticatedUserIsSuperuser = rform->rolsuper;
+	AuthenticatedUserIsSuperuser = (rform->rolattr & ROLE_ATTR_SUPERUSER);
 
 	/* This sets OuterUserId/CurrentUserId too */
 	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
@@ -394,7 +377,7 @@ InitializeSessionUserId(const char *rolename)
 		/*
 		 * Is role allowed to login at all?
 		 */
-		if (!rform->rolcanlogin)
+		if (!(rform->rolattr & ROLE_ATTR_CANLOGIN))
 			ereport(FATAL,
 					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
 					 errmsg("role \"%s\" is not permitted to log in",
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index c348034..268001f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -762,7 +762,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 	{
 		Assert(!bootstrap);
 
-		if (!superuser() && !has_rolreplication(GetUserId()))
+		if (!have_role_attribute(ROLE_ATTR_REPLICATION))
 			ereport(FATAL,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("must be superuser or replication role to start walsender")));
diff --git a/src/backend/utils/misc/superuser.c b/src/backend/utils/misc/superuser.c
index ff0f947..67d070c 100644
--- a/src/backend/utils/misc/superuser.c
+++ b/src/backend/utils/misc/superuser.c
@@ -58,6 +58,7 @@ superuser_arg(Oid roleid)
 {
 	bool		result;
 	HeapTuple	rtup;
+	RoleAttr	attributes;
 
 	/* Quick out for cache hit */
 	if (OidIsValid(last_roleid) && last_roleid == roleid)
@@ -71,7 +72,8 @@ superuser_arg(Oid roleid)
 	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
 	if (HeapTupleIsValid(rtup))
 	{
-		result = ((Form_pg_authid) GETSTRUCT(rtup))->rolsuper;
+		attributes = ((Form_pg_authid) GETSTRUCT(rtup))->rolattr;
+		result = (attributes & ROLE_ATTR_SUPERUSER);
 		ReleaseSysCache(rtup);
 	}
 	else
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index eb633bc..f638167 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -671,10 +671,16 @@ dumpRoles(PGconn *conn)
 	/* note: rolconfig is dumped later */
 	if (server_version >= 90500)
 		printfPQExpBuffer(buf,
-						  "SELECT oid, rolname, rolsuper, rolinherit, "
-						  "rolcreaterole, rolcreatedb, "
-						  "rolcanlogin, rolconnlimit, rolpassword, "
-						  "rolvaliduntil, rolreplication, rolbypassrls, "
+						  "SELECT oid, rolname, "
+						  "pg_check_role_attribute(oid, 'SUPERUSER') AS rolsuper, "
+						  "pg_check_role_attribute(oid, 'INHERIT') AS rolinherit, "
+						  "pg_check_role_attribute(oid, 'CREATEROLE') AS rolcreaterole, "
+						  "pg_check_role_attribute(oid, 'CREATEDB') AS rolcreatedb, "
+						  "pg_check_role_attribute(oid, 'CANLOGIN') AS rolcanlogin, "
+						  "pg_check_role_attribute(oid, 'REPLICATION') AS rolreplication, "
+						  "pg_check_role_attribute(oid, 'BYPASSRLS') AS rolbypassrls, "
+						  "rolconnlimit, rolpassword, "
+						  "rolvaliduntil, "
 			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
 						  "rolname = current_user AS is_current_user "
 						  "FROM pg_authid "
diff --git a/src/include/catalog/acldefs.h b/src/include/catalog/acldefs.h
new file mode 100644
index 0000000..2dcc174
--- /dev/null
+++ b/src/include/catalog/acldefs.h
@@ -0,0 +1,72 @@
+/*-------------------------------------------------------------------------
+ *
+ * acldefs.h
+ *	  base definitions for ACLs and role attributes
+ *
+ * Portions Copyright (c) 2014, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/acldefs.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACLDEFS_H
+#define ACLDEFS_H
+
+/*
+ * Grantable rights are encoded so that we can OR them together in a bitmask.
+ * The present representation of AclItem limits us to 16 distinct rights,
+ * even though AclMode is defined as uint32.  See utils/acl.h.
+ *
+ * Caution: changing these codes breaks stored ACLs, hence forces initdb.
+ */
+typedef uint32 AclMode;			/* a bitmask of privilege bits */
+
+#define ACL_INSERT		(1<<0)	/* for relations */
+#define ACL_SELECT		(1<<1)
+#define ACL_UPDATE		(1<<2)
+#define ACL_DELETE		(1<<3)
+#define ACL_TRUNCATE	(1<<4)
+#define ACL_REFERENCES	(1<<5)
+#define ACL_TRIGGER		(1<<6)
+#define ACL_EXECUTE		(1<<7)	/* for functions */
+#define ACL_USAGE		(1<<8)	/* for languages, namespaces, FDWs, and
+								 * servers */
+#define ACL_CREATE		(1<<9)	/* for namespaces and databases */
+#define ACL_CREATE_TEMP (1<<10) /* for databases */
+#define ACL_CONNECT		(1<<11) /* for databases */
+#define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
+#define ACL_NO_RIGHTS	0
+/* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
+#define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
+
+#define ACL_ID_PUBLIC	0		/* placeholder for id in a PUBLIC acl item */
+
+
+/*
+ * Role attributes are encoded so that we can OR them together in a bitmask.
+ * The present representation of RoleAttr (defined in acl.h) limits us to 64
+ * distinct rights.
+ *
+ * Note about ROLE_ATTR_ALL: This symbol is used verbatim by genbki.pl, which
+ * means we need to hard-code its value instead of using a symbolic definition.
+ * Therefore, whenever role attributes are changed, this value MUST be updated
+ * manually.
+ */
+
+/* A bitmask for role attributes */
+typedef uint64 RoleAttr;
+
+#define ROLE_ATTR_NONE			0
+#define ROLE_ATTR_SUPERUSER		(1<<0)
+#define ROLE_ATTR_INHERIT		(1<<1)
+#define ROLE_ATTR_CREATEROLE	(1<<2)
+#define ROLE_ATTR_CREATEDB		(1<<3)
+#define ROLE_ATTR_CATUPDATE		(1<<4)
+#define ROLE_ATTR_CANLOGIN		(1<<5)
+#define ROLE_ATTR_REPLICATION	(1<<6)
+#define ROLE_ATTR_BYPASSRLS		(1<<7)
+#define N_ROLE_ATTRIBUTES		8		/* 1 plus the last 1<<x */
+#define ROLE_ATTR_ALL			255		/* (1 << N_ROLE_ATTRIBUTES) - 1 */
+
+
+#endif   /* ACLDEFS_H */
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index 3b63d2b..a45f38d 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -21,6 +21,7 @@
 #ifndef PG_AUTHID_H
 #define PG_AUTHID_H
 
+#include "catalog/acldefs.h"
 #include "catalog/genbki.h"
 
 /*
@@ -45,16 +46,8 @@
 CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
 {
 	NameData	rolname;		/* name of role */
-	bool		rolsuper;		/* read this field via superuser() only! */
-	bool		rolinherit;		/* inherit privileges from other roles? */
-	bool		rolcreaterole;	/* allowed to create more roles? */
-	bool		rolcreatedb;	/* allowed to create databases? */
-	bool		rolcatupdate;	/* allowed to alter catalogs manually? */
-	bool		rolcanlogin;	/* allowed to log in as session user? */
-	bool		rolreplication; /* role used for streaming replication */
-	bool		rolbypassrls;	/* allowed to bypass row level security? */
+	int64		rolattr;		/* role attribute bitmask */
 	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
-
 	/* remaining fields may be null; use heap_getattr to read them! */
 	text		rolpassword;	/* password, if any */
 	timestamptz rolvaliduntil;	/* password expiration time, if any */
@@ -74,28 +67,25 @@ typedef FormData_pg_authid *Form_pg_authid;
  *		compiler constants for pg_authid
  * ----------------
  */
-#define Natts_pg_authid					12
+#define Natts_pg_authid					5
 #define Anum_pg_authid_rolname			1
-#define Anum_pg_authid_rolsuper			2
-#define Anum_pg_authid_rolinherit		3
-#define Anum_pg_authid_rolcreaterole	4
-#define Anum_pg_authid_rolcreatedb		5
-#define Anum_pg_authid_rolcatupdate		6
-#define Anum_pg_authid_rolcanlogin		7
-#define Anum_pg_authid_rolreplication	8
-#define Anum_pg_authid_rolbypassrls		9
-#define Anum_pg_authid_rolconnlimit		10
-#define Anum_pg_authid_rolpassword		11
-#define Anum_pg_authid_rolvaliduntil	12
+#define Anum_pg_authid_rolattr			2
+#define Anum_pg_authid_rolconnlimit		3
+#define Anum_pg_authid_rolpassword		4
+#define Anum_pg_authid_rolvaliduntil	5
+
 
 /* ----------------
  *		initial contents of pg_authid
  *
  * The uppercase quantities will be replaced at initdb time with
  * user choices.
+ *
+ * PGROLATTRALL is substituted by genbki.pl to use the value defined by
+ * ROLE_ATTR_ALL.
  * ----------------
  */
-DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t -1 _null_ _null_));
+DATA(insert OID = 10 ( "POSTGRES" PGROLATTRALL -1 _null_ _null_));
 
 #define BOOTSTRAP_SUPERUSERID 10
 
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index eace352..b4f93af 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5136,6 +5136,19 @@ DESCR("rank of hypothetical row without gaps");
 DATA(insert OID = 3993 ( dense_rank_final	PGNSP PGUID 12 1 0 2276 0 f f f f f f i 2 0 20 "2281 2276" "{2281,2276}" "{i,v}" _null_ _null_	hypothetical_dense_rank_final _null_ _null_ _null_ ));
 DESCR("aggregate final function");
 
+/* role attribute support functions */
+DATA(insert OID = 3994 ( pg_has_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ pg_has_role_attribute_id _null_ _null_ _null_ ));
+DESCR("check role attribute by role oid with superuser bypass check");
+DATA(insert OID = 3995 ( pg_has_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "19 25" _null_ _null_ _null_ _null_ pg_has_role_attribute_name _null_ _null_ _null_ ));
+DESCR("check role attribute by role name with superuser bypass check");
+DATA(insert OID = 3996 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_id _null_ _null_ _null_ ));
+DESCR("check role attribute by role id");
+DATA(insert OID = 3997 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "19 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_name _null_ _null_ _null_ ));
+DESCR("check role attribute by role name");
+DATA(insert OID = 3998 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "20 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_attrs _null_ _null_ _null_ ));
+DESCR("check role attribute");
+DATA(insert OID = 3999 ( pg_all_role_attributes		PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 1009 "20" _null_ _null_ _null_ _null_ pg_all_role_attributes _null_ _null_ _null_));
+DESCR("convert role attributes to string array");
 
 /*
  * Symbolic values for provolatile column: these indicate whether the result
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 458eeb0..ecb5780 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -23,8 +23,10 @@
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 #include "nodes/value.h"
+#include "catalog/acldefs.h"
 #include "utils/lockwaitpolicy.h"
 
+
 /* Possible sources of a Query */
 typedef enum QuerySource
 {
@@ -51,33 +53,6 @@ typedef enum SortByNulls
 	SORTBY_NULLS_LAST
 } SortByNulls;
 
-/*
- * Grantable rights are encoded so that we can OR them together in a bitmask.
- * The present representation of AclItem limits us to 16 distinct rights,
- * even though AclMode is defined as uint32.  See utils/acl.h.
- *
- * Caution: changing these codes breaks stored ACLs, hence forces initdb.
- */
-typedef uint32 AclMode;			/* a bitmask of privilege bits */
-
-#define ACL_INSERT		(1<<0)	/* for relations */
-#define ACL_SELECT		(1<<1)
-#define ACL_UPDATE		(1<<2)
-#define ACL_DELETE		(1<<3)
-#define ACL_TRUNCATE	(1<<4)
-#define ACL_REFERENCES	(1<<5)
-#define ACL_TRIGGER		(1<<6)
-#define ACL_EXECUTE		(1<<7)	/* for functions */
-#define ACL_USAGE		(1<<8)	/* for languages, namespaces, FDWs, and
-								 * servers */
-#define ACL_CREATE		(1<<9)	/* for namespaces and databases */
-#define ACL_CREATE_TEMP (1<<10) /* for databases */
-#define ACL_CONNECT		(1<<11) /* for databases */
-#define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
-#define ACL_NO_RIGHTS	0
-/* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
-#define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
-
 
 /*****************************************************************************
  *	Query Tree
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index a8e3164..4e8d81c 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -30,13 +30,6 @@
 
 
 /*
- * typedef AclMode is declared in parsenodes.h, also the individual privilege
- * bit meanings are defined there
- */
-
-#define ACL_ID_PUBLIC	0		/* placeholder for id in a PUBLIC acl item */
-
-/*
  * AclItem
  *
  * Note: must be same size on all platforms, because the size is hardcoded
@@ -326,7 +319,10 @@ extern bool pg_foreign_data_wrapper_ownercheck(Oid srv_oid, Oid roleid);
 extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
 extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
 extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
-extern bool has_createrole_privilege(Oid roleid);
-extern bool has_bypassrls_privilege(Oid roleid);
+
+/* role attribute check routines */
+extern bool has_role_attribute(Oid roleid, RoleAttr attribute);
+extern bool have_role_attribute(RoleAttr attribute);
+extern bool check_role_attribute(Oid roleid, RoleAttr attribute);
 
 #endif   /* ACL_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2da3002..c8e0e3a 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -106,6 +106,12 @@ extern Datum pg_has_role_id_name(PG_FUNCTION_ARGS);
 extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS);
 extern Datum pg_has_role_name(PG_FUNCTION_ARGS);
 extern Datum pg_has_role_id(PG_FUNCTION_ARGS);
+extern Datum pg_has_role_attribute_id(PG_FUNCTION_ARGS);
+extern Datum pg_has_role_attribute_name(PG_FUNCTION_ARGS);
+extern Datum pg_check_role_attribute_id(PG_FUNCTION_ARGS);
+extern Datum pg_check_role_attribute_name(PG_FUNCTION_ARGS);
+extern Datum pg_check_role_attribute_attrs(PG_FUNCTION_ARGS);
+extern Datum pg_all_role_attributes(PG_FUNCTION_ARGS);
 
 /* bool.c */
 extern Datum boolin(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 80c3351..d105215 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1314,7 +1314,7 @@ pg_group| SELECT pg_authid.rolname AS groname,
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
+  WHERE (NOT pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN'::text));
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     i.relname AS indexname,
@@ -1405,17 +1405,17 @@ pg_replication_slots| SELECT l.slot_name,
    FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, xmin, catalog_xmin, restart_lsn)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
-    pg_authid.rolsuper,
-    pg_authid.rolinherit,
-    pg_authid.rolcreaterole,
-    pg_authid.rolcreatedb,
-    pg_authid.rolcatupdate,
-    pg_authid.rolcanlogin,
-    pg_authid.rolreplication,
+    pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER'::text) AS rolsuper,
+    pg_check_role_attribute(pg_authid.rolattr, 'INHERIT'::text) AS rolinherit,
+    pg_check_role_attribute(pg_authid.rolattr, 'CREATEROLE'::text) AS rolcreaterole,
+    pg_check_role_attribute(pg_authid.rolattr, 'CREATEDB'::text) AS rolcreatedb,
+    pg_check_role_attribute(pg_authid.rolattr, 'CATUPDATE'::text) AS rolcatupdate,
+    pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN'::text) AS rolcanlogin,
+    pg_check_role_attribute(pg_authid.rolattr, 'REPLICATION'::text) AS rolreplication,
+    pg_check_role_attribute(pg_authid.rolattr, 'BYPASSRLS'::text) AS rolbypassrls,
     pg_authid.rolconnlimit,
     '********'::text AS rolpassword,
     pg_authid.rolvaliduntil,
-    pg_authid.rolbypassrls,
     s.setconfig AS rolconfig,
     pg_authid.oid
    FROM (pg_authid
@@ -1608,16 +1608,16 @@ pg_settings| SELECT a.name,
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
-    pg_authid.rolcreatedb AS usecreatedb,
-    pg_authid.rolsuper AS usesuper,
-    pg_authid.rolcatupdate AS usecatupd,
-    pg_authid.rolreplication AS userepl,
+    pg_check_role_attribute(pg_authid.rolattr, 'CREATEDB'::text) AS usecreatedb,
+    pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER'::text) AS usesuper,
+    pg_check_role_attribute(pg_authid.rolattr, 'CATUPDATE'::text) AS usecatupd,
+    pg_check_role_attribute(pg_authid.rolattr, 'REPLICATION'::text) AS userepl,
     pg_authid.rolpassword AS passwd,
     (pg_authid.rolvaliduntil)::abstime AS valuntil,
     s.setconfig AS useconfig
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
-  WHERE pg_authid.rolcanlogin;
+  WHERE pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN'::text);
 pg_stat_activity| SELECT s.datid,
     d.datname,
     s.pid,
#34Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#33)
Re: Role Attribute Bitmask Catalog Representation

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Alvaro Herrera wrote:

I think we should create a new header file (maybe acltypes.h or
acldefs.h), which only contains the AclMode and RoleAttr typedefs and
their associated defines; that one would be included from parsenodes.h,
acl.h and pg_authid.h. Everything else would be in acl.h. So code that
currently checks permissions using only acl.h do not need any extra
includes.

I propose this patch on top of Adam's v5. Also included is a full patch
against master.

Thanks! I've just read through your changes to Adam's v5 and they all
look reasonable to me. I agree that having acldefs.h with the #define's
is nicer and cleaner and reduces the amount of including needed for
pg_authid. I also like that it removes those ACL_X definitions from
parsenodes.h.

Thanks also for the whiteline/line-wrap improvements and user.c cleanup,
nice that we don't need all of those individual variables now that we're
using a bitmask.

Unrelated: when changing from unified to context format, I saw
filterdiff fail to produce a complete patch, skipping some hunks in its
output. My first impression was that I had dropped some hunks in git,
so I wasted some time comparing v5 and v6 by hand before remembering
that Michael Paquier had mentioned this problem previously.

Ugh, that's definitely frustrating.. Will keep it in mind.

Thanks again!

Stephen

#35Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Alvaro Herrera (#33)
1 attachment(s)
Re: Role Attribute Bitmask Catalog Representation

Alvaro and Stephen,

I propose this patch on top of Adam's v5. Also included is a full patch

against master.

I have attached an updated patch for review
(role-attribute-bitmask-v7.patch).

This patch incorporates the 'v5a' patch proposed by Alvaro, input
validation (Assert) check in 'check_role_attribute' and the documentation
updates requested by Stephen.

Thanks,
Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com

Attachments:

role-attribute-bitmask-v7.patchtext/x-patch; charset=US-ASCII; name=role-attribute-bitmask-v7.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
new file mode 100644
index 9ceb96b..9470916
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 1391,1479 ****
       </row>
  
       <row>
!       <entry><structfield>rolsuper</structfield></entry>
!       <entry><type>bool</type></entry>
        <entry>Role has superuser privileges</entry>
       </row>
  
       <row>
!       <entry><structfield>rolinherit</structfield></entry>
!       <entry><type>bool</type></entry>
!       <entry>Role automatically inherits privileges of roles it is a
!        member of</entry>
       </row>
  
       <row>
!       <entry><structfield>rolcreaterole</structfield></entry>
!       <entry><type>bool</type></entry>
        <entry>Role can create more roles</entry>
       </row>
  
       <row>
!       <entry><structfield>rolcreatedb</structfield></entry>
!       <entry><type>bool</type></entry>
        <entry>Role can create databases</entry>
       </row>
  
       <row>
!       <entry><structfield>rolcatupdate</structfield></entry>
!       <entry><type>bool</type></entry>
        <entry>
         Role can update system catalogs directly.  (Even a superuser cannot do
         this unless this column is true)
        </entry>
       </row>
  
       <row>
!       <entry><structfield>rolcanlogin</structfield></entry>
!       <entry><type>bool</type></entry>
        <entry>
         Role can log in. That is, this role can be given as the initial
         session authorization identifier
        </entry>
       </row>
  
       <row>
!       <entry><structfield>rolreplication</structfield></entry>
!       <entry><type>bool</type></entry>
        <entry>
         Role is a replication role. That is, this role can initiate streaming
         replication (see <xref linkend="streaming-replication">) and set/unset
         the system backup mode using <function>pg_start_backup</> and
         <function>pg_stop_backup</>
        </entry>
       </row>
  
       <row>
!       <entry><structfield>rolconnlimit</structfield></entry>
!       <entry><type>int4</type></entry>
!       <entry>
!        For roles that can log in, this sets maximum number of concurrent
!        connections this role can make.  -1 means no limit.
!       </entry>
!      </row>
! 
!      <row>
!       <entry><structfield>rolpassword</structfield></entry>
!       <entry><type>text</type></entry>
        <entry>
!        Password (possibly encrypted); null if none.  If the password
!        is encrypted, this column will begin with the string <literal>md5</>
!        followed by a 32-character hexadecimal MD5 hash.  The MD5 hash
!        will be of the user's password concatenated to their user name.
!        For example, if user <literal>joe</> has password <literal>xyzzy</>,
!        <productname>PostgreSQL</> will store the md5 hash of
!        <literal>xyzzyjoe</>.  A password that does not follow that
!        format is assumed to be unencrypted.
        </entry>
       </row>
  
-      <row>
-       <entry><structfield>rolvaliduntil</structfield></entry>
-       <entry><type>timestamptz</type></entry>
-       <entry>Password expiry time (only used for password authentication);
-        null if no expiration</entry>
-      </row>
      </tbody>
     </tgroup>
    </table>
--- 1391,1524 ----
       </row>
  
       <row>
!       <entry><structfield>rolattr</structfield></entry>
!       <entry><type>bigint</type></entry>
!       <entry>
!        Role attributes; see <xref linkend="catalog-rolattr-bitmap-table"> and
!        <xref linkend="sql-createrole"> for details
!       </entry>
!      </row>
! 
!      <row>
!       <entry><structfield>rolconnlimit</structfield></entry>
!       <entry><type>int4</type></entry>
!       <entry>
!        For roles that can log in, this sets maximum number of concurrent
!        connections this role can make.  -1 means no limit.
!       </entry>
!      </row>
! 
!      <row>
!       <entry><structfield>rolpassword</structfield></entry>
!       <entry><type>text</type></entry>
!       <entry>
!        Password (possibly encrypted); null if none.  If the password
!        is encrypted, this column will begin with the string <literal>md5</>
!        followed by a 32-character hexadecimal MD5 hash.  The MD5 hash
!        will be of the user's password concatenated to their user name.
!        For example, if user <literal>joe</> has password <literal>xyzzy</>,
!        <productname>PostgreSQL</> will store the md5 hash of
!        <literal>xyzzyjoe</>.  A password that does not follow that
!        format is assumed to be unencrypted.
!       </entry>
!      </row>
! 
!      <row>
!       <entry><structfield>rolvaliduntil</structfield></entry>
!       <entry><type>timestamptz</type></entry>
!       <entry>Password expiry time (only used for password authentication);
!        null if no expiration</entry>
!      </row>
!     </tbody>
!    </tgroup>
!   </table>
! 
!   <table id="catalog-rolattr-bitmap-table">
!    <title>Attributes in <structfield>rolattr</></title>
! 
!    <tgroup cols="4">
!     <thead>
!      <row>
!       <entry>Attribute</entry>
!       <entry>CREATE ROLE Option</entry>
!       <entry>Description</entry>
!       <entry>Position</entry>
!      </row>
!     </thead>
! 
!     <tbody>
!      <row>
!       <entry>Superuser</entry>
!       <entry>SUPERUSER</entry>
        <entry>Role has superuser privileges</entry>
+       <entry><literal>0</literal></entry>
       </row>
  
       <row>
!       <entry>Inherit</entry>
!       <entry>INHERIT</entry>
!       <entry>
!        Role automatically inherits privileges of roles it is a member of
!       </entry>
!       <entry><literal>1</literal></entry>
       </row>
  
       <row>
!       <entry>Create Role</entry>
!       <entry>CREATEROLE</entry>
        <entry>Role can create more roles</entry>
+       <entry><literal>2</literal></entry>
       </row>
  
       <row>
!       <entry>Create DB</entry>
!       <entry>CREATEDB</entry>
        <entry>Role can create databases</entry>
+       <entry><literal>3</literal></entry>
       </row>
  
       <row>
!       <entry>Catalog Update</entry>
!       <entry>CATUPDATE</entry>
        <entry>
         Role can update system catalogs directly.  (Even a superuser cannot do
         this unless this column is true)
        </entry>
+       <entry><literal>4</literal></entry>
       </row>
  
       <row>
!       <entry>Can Login</entry>
!       <entry>LOGIN</entry>
        <entry>
         Role can log in. That is, this role can be given as the initial
         session authorization identifier
        </entry>
+       <entry><literal>5</literal></entry>
       </row>
  
       <row>
!       <entry>Replication</entry>
!       <entry>REPLICATION</entry>
        <entry>
         Role is a replication role. That is, this role can initiate streaming
         replication (see <xref linkend="streaming-replication">) and set/unset
         the system backup mode using <function>pg_start_backup</> and
         <function>pg_stop_backup</>
        </entry>
+       <entry><literal>6</literal></entry>
       </row>
  
       <row>
!       <entry>Bypass Row Level Security</entry>
!       <entry>BYPASSRLS</entry>
        <entry>
!        Role can bypass row level security policies when <literal>row_security</>
!        is set <literal>off</>
        </entry>
+       <entry><literal>7</literal></entry>
       </row>
  
      </tbody>
     </tgroup>
    </table>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
new file mode 100644
index 6f30946..0c9b890
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT has_function_privilege('joeuser',
*** 15139,15144 ****
--- 15139,15260 ----
      are immediately available without doing <command>SET ROLE</>.
     </para>
  
+    <para>
+     <xref linkend="functions-info-role-attribute-table"> lists functions that
+     allow the user to query role attribute information programmatically.
+    </para>
+ 
+    <table id="functions-info-role-attribute-table">
+     <title>Role Attribute Inquiry Functions</title>
+     <tgroup cols="3">
+      <thead>
+       <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry></row>
+      </thead>
+      <tbody>
+       <row>
+        <entry><literal><function>pg_has_role_attribute(role, attribute)</function></literal></entry>
+        <entry><type>boolean</type></entry>
+        <entry>does role have the permissions allowed by named attribute</entry>
+       </row>
+       <row>
+        <entry><literal><function>pg_check_role_attribute(role, attribute)</function></literal></entry>
+        <entry><type>boolean</type></entry>
+        <entry>does role have the named attribute</entry>
+       </row>
+       <row>
+        <entry><literal><function>pg_check_role_attribute(role_attributes, attribute)</function></literal></entry>
+        <entry><type>boolean</type></entry>
+        <entry>is attribute set in bitmap of role attributes</entry>
+       </row>
+       <row>
+        <entry><literal><function>pg_all_role_attributes(role_attributes)</function></literal></entry>
+        <entry><type>boolean</type></entry>
+        <entry>convert bitmap of role attribute representation to string array</entry>
+       </row>
+      </tbody>
+     </tgroup>
+    </table>
+ 
+    <para>
+     <function>pg_has_role_attribute</function> checks the attribute permissions
+     given to a role.  It will always return <literal>true</literal> for roles
+     with superuser privileges unless the attribute being checked is
+     <literal>CATUPDATE</literal> (superuser cannot bypass
+     <literal>CATUPDATE</literal> permissions). The role can be specified by name
+     and by OID. The attribute is specified by a text string which must evaluate
+     to one of the following role attributes:
+     <literal>SUPERUSER</literal>,
+     <literal>INHERIT</literal>,
+     <literal>CREATEROLE</literal>,
+     <literal>CREATEDB</literal>,
+     <literal>CATUPDATE</literal>,
+     <literal>CANLOGIN</literal>,
+     <literal>REPLICATION</literal>, or
+     <literal>BYPASSRLS</literal>. See <xref linkend="sql-createrole"> for more
+     information. For example:
+ <programlisting>
+ SELECT pg_has_role_attribute('joe', 'SUPERUSER');
+  pg_has_role_attribute 
+ -----------------------
+  f
+ (1 row)
+ 
+ SELECT rolname, pg_has_role_attribute(oid, 'INHERIT') AS rolinherit FROM pg_roles;
+  rolname  | rolinherit 
+ ----------+------------
+  postgres | t
+  joe      | t
+ (2 rows)
+ </programlisting>
+    </para>
+ 
+    <para>
+     <function>pg_check_role_attribute</function> check the attribute value given
+     to a role.  The role can be specified by name and by OID.  The attribute is
+     specified by a text string which must evaluate to a valid role attribute (see
+     <function>pg_has_role_attribute</function>).  A third variant of this function
+     allows for a bitmap representation (<literal>bigint</literal>) of attributes
+     to be given instead of a role. For example:
+ <programlisting>
+ SELECT pg_check_role_attribute('joe', 'SUPERUSER');
+  pg_check_role_attribute 
+ -------------------------
+  f
+ (1 row)
+ 
+ SELECT rolname, pg_check_role_attribute(oid, 'INHERIT') as rolinherit FROM pg_roles;
+  rolname  | rolinherit 
+ ----------+------------
+  postgres | t
+  joe      | t
+ (2 rows)
+  t
+ (1 row)
+ 
+ 
+ SELECT rolname, pg_check_role_attribute(rolattr, 'SUPERUSER') AS rolsuper FROM pg_authid;
+  rolname  | rolsuper 
+ ----------+----------
+  postgres | t
+  joe      | f
+ (2 rows)
+ </programlisting>
+    </para>
+ 
+    <para>
+     <function>pg_all_role_attributes</function> convert a set of role attributes
+     represented by an <literal>bigint</literal> bitmap to a string array.
+     Example:
+ <programlisting>
+ SELECT rolname, pg_all_role_attributes(rolattr) AS attributes FROM pg_authid;
+  rolname  |                                          attributes                                           
+ ----------+-----------------------------------------------------------------------------------------------
+  postgres | {Superuser,Inherit,"Create Role","Create DB","Catalog Update",Login,Replication,"Bypass RLS"}
+  joe      | {Inherit,Login}
+ (2 rows)
+ </programlisting>
+    </para>
+ 
    <para>
     <xref linkend="functions-info-schema-table"> shows functions that
     determine whether a certain object is <firstterm>visible</> in the
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
new file mode 100644
index 133143d..3181a79
*** a/src/backend/access/transam/xlogfuncs.c
--- b/src/backend/access/transam/xlogfuncs.c
***************
*** 27,32 ****
--- 27,33 ----
  #include "miscadmin.h"
  #include "replication/walreceiver.h"
  #include "storage/smgr.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/numeric.h"
  #include "utils/guc.h"
*************** pg_start_backup(PG_FUNCTION_ARGS)
*** 54,60 ****
  
  	backupidstr = text_to_cstring(backupid);
  
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		   errmsg("must be superuser or replication role to run a backup")));
--- 55,61 ----
  
  	backupidstr = text_to_cstring(backupid);
  
! 	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		   errmsg("must be superuser or replication role to run a backup")));
*************** pg_stop_backup(PG_FUNCTION_ARGS)
*** 82,88 ****
  {
  	XLogRecPtr	stoppoint;
  
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		 (errmsg("must be superuser or replication role to run a backup"))));
--- 83,89 ----
  {
  	XLogRecPtr	stoppoint;
  
! 	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  		 (errmsg("must be superuser or replication role to run a backup"))));
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
new file mode 100644
index a403c64..a6de2ff
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
*************** all: $(BKIFILES) schemapg.h
*** 28,34 ****
  # indexing.h had better be last, and toasting.h just before it.
  
  POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
! 	pg_proc.h pg_type.h pg_attribute.h pg_class.h \
  	pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
  	pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
  	pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \
--- 28,34 ----
  # indexing.h had better be last, and toasting.h just before it.
  
  POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
! 	acldefs.h pg_proc.h pg_type.h pg_attribute.h pg_class.h \
  	pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
  	pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
  	pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
new file mode 100644
index d30612c..cd60739
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
*************** aclcheck_error_type(AclResult aclerr, Oi
*** 3423,3448 ****
  }
  
  
- /* Check if given user has rolcatupdate privilege according to pg_authid */
- static bool
- has_rolcatupdate(Oid roleid)
- {
- 	bool		rolcatupdate;
- 	HeapTuple	tuple;
- 
- 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (!HeapTupleIsValid(tuple))
- 		ereport(ERROR,
- 				(errcode(ERRCODE_UNDEFINED_OBJECT),
- 				 errmsg("role with OID %u does not exist", roleid)));
- 
- 	rolcatupdate = ((Form_pg_authid) GETSTRUCT(tuple))->rolcatupdate;
- 
- 	ReleaseSysCache(tuple);
- 
- 	return rolcatupdate;
- }
- 
  /*
   * Relay for the various pg_*_mask routines depending on object kind
   */
--- 3423,3428 ----
*************** pg_class_aclmask(Oid table_oid, Oid role
*** 3630,3636 ****
  	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
  		IsSystemClass(table_oid, classForm) &&
  		classForm->relkind != RELKIND_VIEW &&
! 		!has_rolcatupdate(roleid) &&
  		!allowSystemTableMods)
  	{
  #ifdef ACLDEBUG
--- 3610,3616 ----
  	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
  		IsSystemClass(table_oid, classForm) &&
  		classForm->relkind != RELKIND_VIEW &&
! 		!has_role_attribute(roleid, ROLE_ATTR_CATUPDATE) &&
  		!allowSystemTableMods)
  	{
  #ifdef ACLDEBUG
*************** pg_extension_ownercheck(Oid ext_oid, Oid
*** 5051,5102 ****
  }
  
  /*
!  * Check whether specified role has CREATEROLE privilege (or is a superuser)
   *
!  * Note: roles do not have owners per se; instead we use this test in
!  * places where an ownership-like permissions test is needed for a role.
!  * Be sure to apply it to the role trying to do the operation, not the
!  * role being operated on!	Also note that this generally should not be
!  * considered enough privilege if the target role is a superuser.
!  * (We don't handle that consideration here because we want to give a
!  * separate error message for such cases, so the caller has to deal with it.)
   */
  bool
! has_createrole_privilege(Oid roleid)
  {
! 	bool		result = false;
! 	HeapTuple	utup;
! 
! 	/* Superusers bypass all permission checking. */
! 	if (superuser_arg(roleid))
  		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  bool
! has_bypassrls_privilege(Oid roleid)
  {
! 	bool		result = false;
! 	HeapTuple	utup;
  
! 	/* Superusers bypass all permission checking. */
! 	if (superuser_arg(roleid))
! 		return true;
  
! 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 	if (HeapTupleIsValid(utup))
! 	{
! 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolbypassrls;
! 		ReleaseSysCache(utup);
! 	}
! 	return result;
  }
  
  /*
--- 5031,5117 ----
  }
  
  /*
!  * has_role_attribute
!  *   Check if the role with the specified id has been assigned a specific role
!  *   attribute.
   *
!  * roleid - the oid of the role to check.
!  * attribute - the attribute to check.
!  *
!  * Note: Use this function for role attribute permission checking as it
!  * accounts for superuser status.  It will always return true for roles with
!  * superuser privileges unless the attribute being checked is CATUPDATE
!  * (superusers are not allowed to bypass CATUPDATE permissions).
!  *
!  * Note: roles do not have owners per se; instead we use this test in places
!  * where an ownership-like permissions test is needed for a role.  Be sure to
!  * apply it to the role trying to do the operation, not the role being operated
!  * on!  Also note that this generally should not be considered enough privilege
!  * if the target role is a superuser.  (We don't handle that consideration here
!  * because we want to give a separate error message for such cases, so the
!  * caller has to deal with it.)
   */
  bool
! has_role_attribute(Oid roleid, RoleAttr attribute)
  {
! 	/*
! 	 * Superusers bypass all permission checking except in the case of CATUPDATE
! 	 */
! 	if (!(attribute & ROLE_ATTR_CATUPDATE) && superuser_arg(roleid))
  		return true;
  
! 	return check_role_attribute(roleid, attribute);
  }
  
+ /*
+  * have_role_attribute
+  *   Convenience function for checking if the role id returned by GetUserId()
+  *   has been assigned a specific role attribute.
+  *
+  * attribute - the attribute to check.
+  */
  bool
! have_role_attribute(RoleAttr attribute)
  {
! 	return has_role_attribute(GetUserId(), attribute);
! }
  
! /*
!  * check_role_attribute
!  *   Check if the role with the specified id has been assigned a specific role
!  *   attribute.
!  *
!  * roleid - the oid of the role to check.
!  * attribute - the attribute to check.
!  *
!  * Note: This function should only be used for checking the value of an
!  * individual attribute in the rolattr bitmap and should *not* be used for
!  * permission checking. For the purposes of permission checking use
!  * 'has_role_attribute' instead.
!  */
! bool
! check_role_attribute(Oid roleid, RoleAttr attribute)
! {
! 	RoleAttr	attributes;
! 	HeapTuple	tuple;
  
! 	/* ROLE_ATTR_NONE (zero) is not a valid attribute */
! 	Assert(attribute != ROLE_ATTR_NONE);
! 
! 	/* Check that only one bit is set in 'attribute' */
! 	Assert(!(attribute & (attribute - 1)));
! 
! 	tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
! 
! 	if (!HeapTupleIsValid(tuple))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("role with OID %u does not exist", roleid)));
! 
! 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 	ReleaseSysCache(tuple);
! 
! 	return (attributes & attribute);
  }
  
  /*
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
new file mode 100644
index ca89879..415ac17
*** a/src/backend/catalog/genbki.pl
--- b/src/backend/catalog/genbki.pl
*************** my $BOOTSTRAP_SUPERUSERID =
*** 90,95 ****
--- 90,97 ----
    find_defined_symbol('pg_authid.h', 'BOOTSTRAP_SUPERUSERID');
  my $PG_CATALOG_NAMESPACE =
    find_defined_symbol('pg_namespace.h', 'PG_CATALOG_NAMESPACE');
+ my $ROLE_ATTR_ALL =
+   find_defined_symbol('acldefs.h', 'ROLE_ATTR_ALL');
  
  # Read all the input header files into internal data structures
  my $catalogs = Catalog::Catalogs(@input_files);
*************** foreach my $catname (@{ $catalogs->{name
*** 144,149 ****
--- 146,152 ----
  			# substitute constant values we acquired above
  			$row->{bki_values} =~ s/\bPGUID\b/$BOOTSTRAP_SUPERUSERID/g;
  			$row->{bki_values} =~ s/\bPGNSP\b/$PG_CATALOG_NAMESPACE/g;
+ 			$row->{bki_values} =~ s/\bPGROLATTRALL/$ROLE_ATTR_ALL/g;
  
  			# Save pg_type info for pg_attribute processing below
  			if ($catname eq 'pg_type')
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
new file mode 100644
index a036c62..87b6d8c
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
*************** CREATE VIEW user_mapping_options AS
*** 2884,2890 ****
             CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
             CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                         OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
!                        OR (SELECT rolsuper FROM pg_authid WHERE rolname = current_user) THEN (pg_options_to_table(um.umoptions)).option_value
                       ELSE NULL END AS character_data) AS option_value
      FROM _pg_user_mappings um;
  
--- 2884,2895 ----
             CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
             CAST(CASE WHEN (umuser <> 0 AND authorization_identifier = current_user)
                         OR (umuser = 0 AND pg_has_role(srvowner, 'USAGE'))
!                        OR (
!                             SELECT pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER') AS rolsuper
!                             FROM pg_authid
!                             WHERE rolname = current_user
!                           )
!                        THEN (pg_options_to_table(um.umoptions)).option_value
                       ELSE NULL END AS character_data) AS option_value
      FROM _pg_user_mappings um;
  
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
new file mode 100644
index e261307..6df7e4d
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
*************** check_object_ownership(Oid roleid, Objec
*** 1310,1316 ****
  			}
  			else
  			{
! 				if (!has_createrole_privilege(roleid))
  					ereport(ERROR,
  							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  							 errmsg("must have CREATEROLE privilege")));
--- 1310,1316 ----
  			}
  			else
  			{
! 				if (!has_role_attribute(roleid, ROLE_ATTR_CREATEROLE))
  					ereport(ERROR,
  							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  							 errmsg("must have CREATEROLE privilege")));
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
new file mode 100644
index 22b8cee..ae93832
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 9,25 ****
  CREATE VIEW pg_roles AS
      SELECT
          rolname,
!         rolsuper,
!         rolinherit,
!         rolcreaterole,
!         rolcreatedb,
!         rolcatupdate,
!         rolcanlogin,
!         rolreplication,
          rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
-         rolbypassrls,
          setconfig as rolconfig,
          pg_authid.oid
      FROM pg_authid LEFT JOIN pg_db_role_setting s
--- 9,25 ----
  CREATE VIEW pg_roles AS
      SELECT
          rolname,
!         pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER') AS rolsuper,
!         pg_check_role_attribute(pg_authid.rolattr, 'INHERIT') AS rolinherit,
!         pg_check_role_attribute(pg_authid.rolattr, 'CREATEROLE') AS rolcreaterole,
!         pg_check_role_attribute(pg_authid.rolattr, 'CREATEDB') AS rolcreatedb,
!         pg_check_role_attribute(pg_authid.rolattr, 'CATUPDATE') AS rolcatupdate,
!         pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN') AS rolcanlogin,
!         pg_check_role_attribute(pg_authid.rolattr, 'REPLICATION') AS rolreplication,
!         pg_check_role_attribute(pg_authid.rolattr, 'BYPASSRLS') AS rolbypassrls,
          rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
          setconfig as rolconfig,
          pg_authid.oid
      FROM pg_authid LEFT JOIN pg_db_role_setting s
*************** CREATE VIEW pg_shadow AS
*** 29,44 ****
      SELECT
          rolname AS usename,
          pg_authid.oid AS usesysid,
!         rolcreatedb AS usecreatedb,
!         rolsuper AS usesuper,
!         rolcatupdate AS usecatupd,
!         rolreplication AS userepl,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          setconfig AS useconfig
      FROM pg_authid LEFT JOIN pg_db_role_setting s
      ON (pg_authid.oid = setrole AND setdatabase = 0)
!     WHERE rolcanlogin;
  
  REVOKE ALL on pg_shadow FROM public;
  
--- 29,44 ----
      SELECT
          rolname AS usename,
          pg_authid.oid AS usesysid,
!         pg_check_role_attribute(pg_authid.rolattr, 'CREATEDB') AS usecreatedb,
!         pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER') AS usesuper,
!         pg_check_role_attribute(pg_authid.rolattr, 'CATUPDATE') AS usecatupd,
!         pg_check_role_attribute(pg_authid.rolattr, 'REPLICATION') AS userepl,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          setconfig AS useconfig
      FROM pg_authid LEFT JOIN pg_db_role_setting s
      ON (pg_authid.oid = setrole AND setdatabase = 0)
!     WHERE pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN');
  
  REVOKE ALL on pg_shadow FROM public;
  
*************** CREATE VIEW pg_group AS
*** 48,54 ****
          oid AS grosysid,
          ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
      FROM pg_authid
!     WHERE NOT rolcanlogin;
  
  CREATE VIEW pg_user AS
      SELECT
--- 48,54 ----
          oid AS grosysid,
          ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist
      FROM pg_authid
!     WHERE NOT pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN');
  
  CREATE VIEW pg_user AS
      SELECT
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
new file mode 100644
index 1a5244c..c079168
*** a/src/backend/commands/dbcommands.c
--- b/src/backend/commands/dbcommands.c
*************** static bool get_db_info(const char *name
*** 85,91 ****
  			Oid *dbLastSysOidP, TransactionId *dbFrozenXidP,
  			MultiXactId *dbMinMultiP,
  			Oid *dbTablespace, char **dbCollate, char **dbCtype);
- static bool have_createdb_privilege(void);
  static void remove_dbtablespaces(Oid db_id);
  static bool check_db_file_conflict(Oid db_id);
  static int	errdetail_busy_db(int notherbackends, int npreparedxacts);
--- 85,90 ----
*************** createdb(const CreatedbStmt *stmt)
*** 291,297 ****
  	 * "giveaway" attacks.  Note that a superuser will always have both of
  	 * these privileges a fortiori.
  	 */
! 	if (!have_createdb_privilege())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to create database")));
--- 290,296 ----
  	 * "giveaway" attacks.  Note that a superuser will always have both of
  	 * these privileges a fortiori.
  	 */
! 	if (!have_role_attribute(ROLE_ATTR_CREATEDB))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to create database")));
*************** RenameDatabase(const char *oldname, cons
*** 965,971 ****
  					   oldname);
  
  	/* must have createdb rights */
! 	if (!have_createdb_privilege())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to rename database")));
--- 964,970 ----
  					   oldname);
  
  	/* must have createdb rights */
! 	if (!have_role_attribute(ROLE_ATTR_CREATEDB))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to rename database")));
*************** AlterDatabaseOwner(const char *dbname, O
*** 1623,1629 ****
  		 * databases.  Because superusers will always have this right, we need
  		 * no special case for them.
  		 */
! 		if (!have_createdb_privilege())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				   errmsg("permission denied to change owner of database")));
--- 1622,1628 ----
  		 * databases.  Because superusers will always have this right, we need
  		 * no special case for them.
  		 */
! 		if (!have_role_attribute(ROLE_ATTR_CREATEDB))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				   errmsg("permission denied to change owner of database")));
*************** get_db_info(const char *name, LOCKMODE l
*** 1802,1827 ****
  	return result;
  }
  
- /* Check if current user has createdb privileges */
- static bool
- have_createdb_privilege(void)
- {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
- 	/* Superusers can always do everything */
- 	if (superuser())
- 		return true;
- 
- 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId()));
- 	if (HeapTupleIsValid(utup))
- 	{
- 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreatedb;
- 		ReleaseSysCache(utup);
- 	}
- 	return result;
- }
- 
  /*
   * Remove tablespace directories
   *
--- 1801,1806 ----
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
new file mode 100644
index 1a73fd8..564f77a
*** a/src/backend/commands/user.c
--- b/src/backend/commands/user.c
*************** static void DelRoleMems(const char *role
*** 56,69 ****
  			bool admin_opt);
  
  
- /* Check if current user has createrole privileges */
- static bool
- have_createrole_privilege(void)
- {
- 	return has_createrole_privilege(GetUserId());
- }
- 
- 
  /*
   * CREATE ROLE
   */
--- 56,61 ----
*************** CreateRole(CreateRoleStmt *stmt)
*** 81,93 ****
  	char	   *password = NULL;	/* user password */
  	bool		encrypt_password = Password_encryption; /* encrypt password? */
  	char		encrypted_password[MD5_PASSWD_LEN + 1];
! 	bool		issuper = false;	/* Make the user a superuser? */
! 	bool		inherit = true; /* Auto inherit privileges? */
! 	bool		createrole = false;		/* Can this user create roles? */
! 	bool		createdb = false;		/* Can the user create databases? */
! 	bool		canlogin = false;		/* Can this user login? */
! 	bool		isreplication = false;	/* Is this a replication role? */
! 	bool		bypassrls = false;		/* Is this a row security enabled role? */
  	int			connlimit = -1; /* maximum connections allowed */
  	List	   *addroleto = NIL;	/* roles to make this a member of */
  	List	   *rolemembers = NIL;		/* roles to be members of this role */
--- 73,79 ----
  	char	   *password = NULL;	/* user password */
  	bool		encrypt_password = Password_encryption; /* encrypt password? */
  	char		encrypted_password[MD5_PASSWD_LEN + 1];
! 	RoleAttr	attributes;
  	int			connlimit = -1; /* maximum connections allowed */
  	List	   *addroleto = NIL;	/* roles to make this a member of */
  	List	   *rolemembers = NIL;		/* roles to be members of this role */
*************** CreateRole(CreateRoleStmt *stmt)
*** 109,121 ****
  	DefElem    *dvalidUntil = NULL;
  	DefElem    *dbypassRLS = NULL;
  
! 	/* The defaults can vary depending on the original statement type */
  	switch (stmt->stmt_type)
  	{
  		case ROLESTMT_ROLE:
  			break;
  		case ROLESTMT_USER:
! 			canlogin = true;
  			/* may eventually want inherit to default to false here */
  			break;
  		case ROLESTMT_GROUP:
--- 95,111 ----
  	DefElem    *dvalidUntil = NULL;
  	DefElem    *dbypassRLS = NULL;
  
! 	/*
! 	 * Every role has INHERIT by default, and CANLOGIN depends on the statement
! 	 * type.
! 	 */
! 	attributes = ROLE_ATTR_INHERIT;
  	switch (stmt->stmt_type)
  	{
  		case ROLESTMT_ROLE:
  			break;
  		case ROLESTMT_USER:
! 			attributes |= ROLE_ATTR_CANLOGIN;
  			/* may eventually want inherit to default to false here */
  			break;
  		case ROLESTMT_GROUP:
*************** CreateRole(CreateRoleStmt *stmt)
*** 249,266 ****
  
  	if (dpassword && dpassword->arg)
  		password = strVal(dpassword->arg);
  	if (dissuper)
! 		issuper = intVal(dissuper->arg) != 0;
  	if (dinherit)
! 		inherit = intVal(dinherit->arg) != 0;
  	if (dcreaterole)
! 		createrole = intVal(dcreaterole->arg) != 0;
  	if (dcreatedb)
! 		createdb = intVal(dcreatedb->arg) != 0;
  	if (dcanlogin)
! 		canlogin = intVal(dcanlogin->arg) != 0;
  	if (disreplication)
! 		isreplication = intVal(disreplication->arg) != 0;
  	if (dconnlimit)
  	{
  		connlimit = intVal(dconnlimit->arg);
--- 239,314 ----
  
  	if (dpassword && dpassword->arg)
  		password = strVal(dpassword->arg);
+ 
+ 	/* Set up role attributes and check permissions to set each of them */
  	if (dissuper)
! 	{
! 		if (intVal(dissuper->arg) != 0)
! 		{
! 			if (!superuser())
! 				ereport(ERROR,
! 						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! 						 errmsg("must be superuser to create superusers")));
! 			attributes |= ROLE_ATTR_SUPERUSER;
! 		}
! 		else
! 			attributes &= ~ROLE_ATTR_SUPERUSER;
! 	}
  	if (dinherit)
! 	{
! 		if (intVal(dinherit->arg) != 0)
! 			attributes |= ROLE_ATTR_INHERIT;
! 		else
! 			attributes &= ~ROLE_ATTR_INHERIT;
! 	}
  	if (dcreaterole)
! 	{
! 		if (intVal(dcreaterole->arg) != 0)
! 			attributes |= ROLE_ATTR_CREATEROLE;
! 		else
! 			attributes &= ~ROLE_ATTR_CREATEROLE;
! 	}
  	if (dcreatedb)
! 	{
! 		if (intVal(dcreatedb->arg) != 0)
! 			attributes |= ROLE_ATTR_CREATEDB;
! 		else
! 			attributes &= ~ROLE_ATTR_CREATEDB;
! 	}
  	if (dcanlogin)
! 	{
! 		if (intVal(dcanlogin->arg) != 0)
! 			attributes |= ROLE_ATTR_CANLOGIN;
! 		else
! 			attributes &= ~ROLE_ATTR_CANLOGIN;
! 	}
  	if (disreplication)
! 	{
! 		if (intVal(disreplication->arg) != 0)
! 		{
! 			if (!superuser())
! 				ereport(ERROR,
! 						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! 						 errmsg("must be superuser to create replication users")));
! 			attributes |= ROLE_ATTR_REPLICATION;
! 		}
! 		else
! 			attributes &= ~ROLE_ATTR_REPLICATION;
! 	}
! 	if (dbypassRLS)
! 	{
! 		if (intVal(dbypassRLS->arg) != 0)
! 		{
! 			if (!superuser())
! 				ereport(ERROR,
! 						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! 						 errmsg("must be superuser to change bypassrls attribute")));
! 			attributes |= ROLE_ATTR_BYPASSRLS;
! 		}
! 		else
! 			attributes &= ~ROLE_ATTR_BYPASSRLS;
! 	}
! 
  	if (dconnlimit)
  	{
  		connlimit = intVal(dconnlimit->arg);
*************** CreateRole(CreateRoleStmt *stmt)
*** 277,314 ****
  		adminmembers = (List *) dadminmembers->arg;
  	if (dvalidUntil)
  		validUntil = strVal(dvalidUntil->arg);
- 	if (dbypassRLS)
- 		bypassrls = intVal(dbypassRLS->arg) != 0;
  
! 	/* Check some permissions first */
! 	if (issuper)
! 	{
! 		if (!superuser())
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! 					 errmsg("must be superuser to create superusers")));
! 	}
! 	else if (isreplication)
! 	{
! 		if (!superuser())
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! 				   errmsg("must be superuser to create replication users")));
! 	}
! 	else if (bypassrls)
! 	{
! 		if (!superuser())
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! 					 errmsg("must be superuser to change bypassrls attribute.")));
! 	}
! 	else
! 	{
! 		if (!have_createrole_privilege())
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! 					 errmsg("permission denied to create role")));
! 	}
  
  	if (strcmp(stmt->role, "public") == 0 ||
  		strcmp(stmt->role, "none") == 0)
--- 325,336 ----
  		adminmembers = (List *) dadminmembers->arg;
  	if (dvalidUntil)
  		validUntil = strVal(dvalidUntil->arg);
  
! 	/* Check permissions */
! 	if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! 				 errmsg("permission denied to create role")));
  
  	if (strcmp(stmt->role, "public") == 0 ||
  		strcmp(stmt->role, "none") == 0)
*************** CreateRole(CreateRoleStmt *stmt)
*** 364,377 ****
  	new_record[Anum_pg_authid_rolname - 1] =
  		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
  
! 	new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
! 	new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
! 	new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
! 	new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
! 	/* superuser gets catupdate right by default */
! 	new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper);
! 	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
! 	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
  	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
  
  	if (password)
--- 386,393 ----
  	new_record[Anum_pg_authid_rolname - 1] =
  		DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
  
! 	new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
! 
  	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
  
  	if (password)
*************** CreateRole(CreateRoleStmt *stmt)
*** 394,401 ****
  	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
  	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
  
- 	new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls);
- 
  	tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
  
  	/*
--- 410,415 ----
*************** AlterRole(AlterRoleStmt *stmt)
*** 508,513 ****
--- 522,528 ----
  	DefElem    *dvalidUntil = NULL;
  	DefElem    *dbypassRLS = NULL;
  	Oid			roleid;
+ 	RoleAttr	attributes;
  
  	/* Extract options from the statement node tree */
  	foreach(option, stmt->options)
*************** AlterRole(AlterRoleStmt *stmt)
*** 658,688 ****
  	roleid = HeapTupleGetOid(tuple);
  
  	/*
! 	 * To mess with a superuser you gotta be superuser; else you need
! 	 * createrole, or just want to change your own password
  	 */
! 	if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper || issuper >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter superusers")));
  	}
! 	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolreplication || isreplication >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter replication users")));
  	}
! 	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolbypassrls || bypassrls >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to change bypassrls attribute")));
  	}
! 	else if (!have_createrole_privilege())
  	{
  		if (!(inherit < 0 &&
  			  createrole < 0 &&
--- 673,706 ----
  	roleid = HeapTupleGetOid(tuple);
  
  	/*
! 	 * To mess with a superuser or a replication user you gotta be superuser;
! 	 * else you need createrole, or just want to change your own password
  	 */
! 
! 	attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 
! 	if ((attributes & ROLE_ATTR_SUPERUSER) || issuper >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter superusers")));
  	}
! 	else if ((attributes & ROLE_ATTR_REPLICATION) || isreplication >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to alter replication users")));
  	}
! 	else if ((attributes & ROLE_ATTR_BYPASSRLS) || bypassrls >= 0)
  	{
  		if (!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to change bypassrls attribute")));
  	}
! 	else if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
  	{
  		if (!(inherit < 0 &&
  			  createrole < 0 &&
*************** AlterRole(AlterRoleStmt *stmt)
*** 743,785 ****
  	 */
  	if (issuper >= 0)
  	{
! 		new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);
! 		new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
! 
! 		new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);
! 		new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true;
  	}
  
  	if (inherit >= 0)
  	{
! 		new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
! 		new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
  	}
  
  	if (createrole >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);
! 		new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
  	}
  
  	if (createdb >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0);
! 		new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true;
  	}
  
  	if (canlogin >= 0)
  	{
! 		new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0);
! 		new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true;
  	}
  
  	if (isreplication >= 0)
  	{
! 		new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication > 0);
! 		new_record_repl[Anum_pg_authid_rolreplication - 1] = true;
  	}
  
  	if (dconnlimit)
  	{
  		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
--- 761,831 ----
  	 */
  	if (issuper >= 0)
  	{
! 		if (issuper > 0)
! 			attributes |= ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE;
! 		else
! 			attributes &= ~(ROLE_ATTR_SUPERUSER | ROLE_ATTR_CATUPDATE);
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (inherit >= 0)
  	{
! 		if (inherit > 0)
! 			attributes |= ROLE_ATTR_INHERIT;
! 		else
! 			attributes &= ~ROLE_ATTR_INHERIT;
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (createrole >= 0)
  	{
! 		if (createrole > 0)
! 			attributes |= ROLE_ATTR_CREATEROLE;
! 		else
! 			attributes &= ~ROLE_ATTR_CREATEROLE;
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (createdb >= 0)
  	{
! 		if (createdb > 0)
! 			attributes |= ROLE_ATTR_CREATEDB;
! 		else
! 			attributes &= ~ROLE_ATTR_CREATEDB;
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (canlogin >= 0)
  	{
! 		if (canlogin > 0)
! 			attributes |= ROLE_ATTR_CANLOGIN;
! 		else
! 			attributes &= ~ROLE_ATTR_CANLOGIN;
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
  	if (isreplication >= 0)
  	{
! 		if (isreplication > 0)
! 			attributes |= ROLE_ATTR_REPLICATION;
! 		else
! 			attributes &= ~ROLE_ATTR_REPLICATION;
! 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
  	}
  
+ 	if (bypassrls >= 0)
+ 	{
+ 		if (bypassrls > 0)
+ 			attributes |= ROLE_ATTR_BYPASSRLS;
+ 		else
+ 			attributes &= ~ROLE_ATTR_BYPASSRLS;
+ 		new_record_repl[Anum_pg_authid_rolattr - 1] = true;
+ 	}
+ 
+ 	/* If any role attributes were set, then update. */
+ 	if (new_record_repl[Anum_pg_authid_rolattr - 1])
+ 		new_record[Anum_pg_authid_rolattr - 1] = Int64GetDatum(attributes);
+ 
  	if (dconnlimit)
  	{
  		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
*************** AlterRole(AlterRoleStmt *stmt)
*** 815,825 ****
  	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
  	new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
  
- 	if (bypassrls >= 0)
- 	{
- 		new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls > 0);
- 		new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
- 	}
  
  	new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
  								  new_record_nulls, new_record_repl);
--- 861,866 ----
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 867,872 ****
--- 908,914 ----
  	HeapTuple	roletuple;
  	Oid			databaseid = InvalidOid;
  	Oid			roleid = InvalidOid;
+ 	RoleAttr	attributes;
  
  	if (stmt->role)
  	{
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 889,895 ****
  		 * To mess with a superuser you gotta be superuser; else you need
  		 * createrole, or just want to change your own settings
  		 */
! 		if (((Form_pg_authid) GETSTRUCT(roletuple))->rolsuper)
  		{
  			if (!superuser())
  				ereport(ERROR,
--- 931,938 ----
  		 * To mess with a superuser you gotta be superuser; else you need
  		 * createrole, or just want to change your own settings
  		 */
! 		attributes = ((Form_pg_authid) GETSTRUCT(roletuple))->rolattr;
! 		if (attributes & ROLE_ATTR_SUPERUSER)
  		{
  			if (!superuser())
  				ereport(ERROR,
*************** AlterRoleSet(AlterRoleSetStmt *stmt)
*** 898,904 ****
  		}
  		else
  		{
! 			if (!have_createrole_privilege() &&
  				HeapTupleGetOid(roletuple) != GetUserId())
  				ereport(ERROR,
  						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 941,947 ----
  		}
  		else
  		{
! 			if (!have_role_attribute(ROLE_ATTR_CREATEROLE) &&
  				HeapTupleGetOid(roletuple) != GetUserId())
  				ereport(ERROR,
  						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
*************** DropRole(DropRoleStmt *stmt)
*** 951,957 ****
  				pg_auth_members_rel;
  	ListCell   *item;
  
! 	if (!have_createrole_privilege())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to drop role")));
--- 994,1000 ----
  				pg_auth_members_rel;
  	ListCell   *item;
  
! 	if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 errmsg("permission denied to drop role")));
*************** DropRole(DropRoleStmt *stmt)
*** 973,978 ****
--- 1016,1022 ----
  		char	   *detail_log;
  		SysScanDesc sscan;
  		Oid			roleid;
+ 		RoleAttr	attributes;
  
  		tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
  		if (!HeapTupleIsValid(tuple))
*************** DropRole(DropRoleStmt *stmt)
*** 1013,1020 ****
  		 * roles but not superuser roles.  This is mainly to avoid the
  		 * scenario where you accidentally drop the last superuser.
  		 */
! 		if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper &&
! 			!superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to drop superusers")));
--- 1057,1064 ----
  		 * roles but not superuser roles.  This is mainly to avoid the
  		 * scenario where you accidentally drop the last superuser.
  		 */
! 		attributes = ((Form_pg_authid) GETSTRUCT(tuple))->rolattr;
! 		if ((attributes & ROLE_ATTR_SUPERUSER) && !superuser())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser to drop superusers")));
*************** RenameRole(const char *oldname, const ch
*** 1128,1133 ****
--- 1172,1178 ----
  	bool		repl_repl[Natts_pg_authid];
  	int			i;
  	Oid			roleid;
+ 	RoleAttr	attributes;
  
  	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
  	dsc = RelationGetDescr(rel);
*************** RenameRole(const char *oldname, const ch
*** 1173,1179 ****
  	/*
  	 * createrole is enough privilege unless you want to mess with a superuser
  	 */
! 	if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
  	{
  		if (!superuser())
  			ereport(ERROR,
--- 1218,1225 ----
  	/*
  	 * createrole is enough privilege unless you want to mess with a superuser
  	 */
! 	attributes = ((Form_pg_authid) GETSTRUCT(oldtuple))->rolattr;
! 	if (attributes & ROLE_ATTR_SUPERUSER)
  	{
  		if (!superuser())
  			ereport(ERROR,
*************** RenameRole(const char *oldname, const ch
*** 1182,1188 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege())
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to rename role")));
--- 1228,1234 ----
  	}
  	else
  	{
! 		if (!have_role_attribute(ROLE_ATTR_CREATEROLE))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("permission denied to rename role")));
*************** AddRoleMems(const char *rolename, Oid ro
*** 1409,1415 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege() &&
  			!is_admin_of_role(grantorId, roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 1455,1461 ----
  	}
  	else
  	{
! 		if (!have_role_attribute(ROLE_ATTR_CREATEROLE) &&
  			!is_admin_of_role(grantorId, roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
*************** DelRoleMems(const char *rolename, Oid ro
*** 1555,1561 ****
  	}
  	else
  	{
! 		if (!have_createrole_privilege() &&
  			!is_admin_of_role(GetUserId(), roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 1601,1607 ----
  	}
  	else
  	{
! 		if (!have_role_attribute(ROLE_ATTR_CREATEROLE) &&
  			!is_admin_of_role(GetUserId(), roleid))
  			ereport(ERROR,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
new file mode 100644
index 6ce8dae..491dc38
*** a/src/backend/commands/variable.c
--- b/src/backend/commands/variable.c
*************** check_session_authorization(char **newva
*** 776,781 ****
--- 776,782 ----
  	Oid			roleid;
  	bool		is_superuser;
  	role_auth_extra *myextra;
+ 	RoleAttr	attributes;
  
  	/* Do nothing for the boot_val default of NULL */
  	if (*newval == NULL)
*************** check_session_authorization(char **newva
*** 800,806 ****
  	}
  
  	roleid = HeapTupleGetOid(roleTup);
! 	is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
  
  	ReleaseSysCache(roleTup);
  
--- 801,808 ----
  	}
  
  	roleid = HeapTupleGetOid(roleTup);
! 	attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
! 	is_superuser = (attributes & ROLE_ATTR_SUPERUSER);
  
  	ReleaseSysCache(roleTup);
  
*************** check_role(char **newval, void **extra,
*** 844,849 ****
--- 846,852 ----
  	Oid			roleid;
  	bool		is_superuser;
  	role_auth_extra *myextra;
+ 	RoleAttr	attributes;
  
  	if (strcmp(*newval, "none") == 0)
  	{
*************** check_role(char **newval, void **extra,
*** 872,878 ****
  		}
  
  		roleid = HeapTupleGetOid(roleTup);
! 		is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
  
  		ReleaseSysCache(roleTup);
  
--- 875,882 ----
  		}
  
  		roleid = HeapTupleGetOid(roleTup);
! 		attributes = ((Form_pg_authid) GETSTRUCT(roleTup))->rolattr;
! 		is_superuser = (attributes & ROLE_ATTR_SUPERUSER);
  
  		ReleaseSysCache(roleTup);
  
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
new file mode 100644
index 1977f09..1a38f56
*** a/src/backend/replication/logical/logicalfuncs.c
--- b/src/backend/replication/logical/logicalfuncs.c
***************
*** 17,34 ****
  
  #include <unistd.h>
  
  #include "fmgr.h"
  #include "funcapi.h"
  #include "miscadmin.h"
- 
- #include "access/xlog_internal.h"
- 
- #include "catalog/pg_type.h"
- 
  #include "nodes/makefuncs.h"
! 
! #include "mb/pg_wchar.h"
! 
  #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/inval.h"
--- 17,30 ----
  
  #include <unistd.h>
  
+ #include "access/xlog_internal.h"
+ #include "catalog/pg_type.h"
  #include "fmgr.h"
  #include "funcapi.h"
+ #include "mb/pg_wchar.h"
  #include "miscadmin.h"
  #include "nodes/makefuncs.h"
! #include "utils/acl.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/inval.h"
***************
*** 36,46 ****
  #include "utils/pg_lsn.h"
  #include "utils/resowner.h"
  #include "utils/lsyscache.h"
- 
  #include "replication/decode.h"
  #include "replication/logical.h"
  #include "replication/logicalfuncs.h"
- 
  #include "storage/fd.h"
  
  /* private date for writing out data */
--- 32,40 ----
*************** XLogRead(char *buf, TimeLineID tli, XLog
*** 205,211 ****
  static void
  check_permissions(void)
  {
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
--- 199,205 ----
  static void
  check_permissions(void)
  {
! 	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
new file mode 100644
index bd4701f..c113a0b
*** a/src/backend/replication/slotfuncs.c
--- b/src/backend/replication/slotfuncs.c
***************
*** 20,32 ****
  #include "replication/slot.h"
  #include "replication/logical.h"
  #include "replication/logicalfuncs.h"
  #include "utils/builtins.h"
  #include "utils/pg_lsn.h"
  
  static void
  check_permissions(void)
  {
! 	if (!superuser() && !has_rolreplication(GetUserId()))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
--- 20,33 ----
  #include "replication/slot.h"
  #include "replication/logical.h"
  #include "replication/logicalfuncs.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/pg_lsn.h"
  
  static void
  check_permissions(void)
  {
! 	if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  				 (errmsg("must be superuser or replication role to use replication slots"))));
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
new file mode 100644
index 6c232dc..f41ad34
*** a/src/backend/rewrite/rowsecurity.c
--- b/src/backend/rewrite/rowsecurity.c
*************** check_enable_rls(Oid relid, Oid checkAsU
*** 521,527 ****
  	 */
  	if (!checkAsUser && row_security == ROW_SECURITY_OFF)
  	{
! 		if (has_bypassrls_privilege(user_id))
  			/* OK to bypass */
  			return RLS_NONE_ENV;
  		else
--- 521,527 ----
  	 */
  	if (!checkAsUser && row_security == ROW_SECURITY_OFF)
  	{
! 		if (has_role_attribute(user_id, ROLE_ATTR_BYPASSRLS))
  			/* OK to bypass */
  			return RLS_NONE_ENV;
  		else
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
new file mode 100644
index dc6eb2c..4c03955
*** a/src/backend/utils/adt/acl.c
--- b/src/backend/utils/adt/acl.c
*************** static Oid	convert_type_name(text *typen
*** 115,120 ****
--- 115,121 ----
  static AclMode convert_type_priv_string(text *priv_type_text);
  static AclMode convert_role_priv_string(text *priv_type_text);
  static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
+ static RoleAttr convert_role_attr_string(text *attr_type_text);
  
  static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue);
  
*************** pg_role_aclcheck(Oid role_oid, Oid rolei
*** 4602,4607 ****
--- 4603,4788 ----
  	return ACLCHECK_NO_PRIV;
  }
  
+ /*
+  * pg_has_role_attribute_id
+  *		Check that the role with the given oid has the given named role
+  *		attribute.
+  *
+  * Note: This function applies superuser checks.  Therefore, if the provided
+  * role is a superuser, then the result will always be true.
+  */
+ Datum
+ pg_has_role_attribute_id(PG_FUNCTION_ARGS)
+ {
+ 	Oid			roleoid = PG_GETARG_OID(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(has_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_has_role_attribute_name
+  *		Check that the named role has the given named role attribute.
+  *
+  * Note: This function applies superuser checks.  Therefore, if the provided
+  * role is a superuser, then the result will always be true.
+  */
+ Datum
+ pg_has_role_attribute_name(PG_FUNCTION_ARGS)
+ {
+ 	Name		rolename = PG_GETARG_NAME(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	Oid			roleoid;
+ 	RoleAttr	attribute;
+ 
+ 	roleoid = get_role_oid(NameStr(*rolename), false);
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(has_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_check_role_attribute_id
+  *		Check that the role with the given oid has the given named role
+  *		attribute.
+  *
+  * Note: This function is different from 'pg_has_role_attribute_id_attr' in that
+  * it does *not* apply any superuser checks.  Therefore, this function will
+  * always return the set value of the attribute, despite the superuser-ness of
+  * the provided role.
+  */
+ Datum
+ pg_check_role_attribute_id(PG_FUNCTION_ARGS)
+ {
+ 	Oid			roleoid = PG_GETARG_OID(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(check_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_check_role_attribute_name
+  *		Check that the named role has the given named role attribute.
+  *
+  * Note: This function is different from 'pg_has_role_attribute_name_attr' in
+  * that it does *not* apply any superuser checks.  Therefore, this function will
+  * always return the set value of the attribute, despite the superuser-ness of
+  * the provided role.
+  */
+ Datum
+ pg_check_role_attribute_name(PG_FUNCTION_ARGS)
+ {
+ 	Name		rolename = PG_GETARG_NAME(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	Oid			roleoid;
+ 	RoleAttr	attribute;
+ 
+ 	roleoid = get_role_oid(NameStr(*rolename), false);
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(check_role_attribute(roleoid, attribute));
+ }
+ 
+ /*
+  * pg_check_role_attribute_attrs
+  *		Check that the named attribute is enabled in the given RoleAttr
+  *		representation of role attributes.
+  */
+ Datum
+ pg_check_role_attribute_attrs(PG_FUNCTION_ARGS)
+ {
+ 	RoleAttr	attributes = PG_GETARG_INT64(0);
+ 	text	   *attr_type_text = PG_GETARG_TEXT_P(1);
+ 	RoleAttr	attribute;
+ 
+ 	attribute = convert_role_attr_string(attr_type_text);
+ 
+ 	PG_RETURN_BOOL(attributes & attribute);
+ }
+ 
+ /*
+  * pg_all_role_attributes
+  *		Convert a RoleAttr representation of role attributes into an array of
+  *		corresponding text values.
+  *
+  * The first and only argument is a RoleAttr (int64) representation of the
+  * role attributes.
+  */
+ Datum
+ pg_all_role_attributes(PG_FUNCTION_ARGS)
+ {
+ 	RoleAttr		attributes = PG_GETARG_INT64(0);
+ 	Datum		   *temp_array;
+ 	ArrayType	   *result;
+ 	int				i = 0;
+ 
+ 	/*
+ 	 * Short-circuit the case for no attributes assigned.
+ 	 */
+ 	if (attributes == ROLE_ATTR_NONE)
+ 		PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));
+ 
+ 	temp_array = (Datum *) palloc(N_ROLE_ATTRIBUTES * sizeof(Datum));
+ 
+ 	/* Determine which attributes are assigned. */
+ 	if (attributes & ROLE_ATTR_SUPERUSER)
+ 		temp_array[i++] = CStringGetTextDatum(_("Superuser"));
+ 	if (attributes & ROLE_ATTR_INHERIT)
+ 		temp_array[i++] = CStringGetTextDatum(_("Inherit"));
+ 	if (attributes & ROLE_ATTR_CREATEROLE)
+ 		temp_array[i++] = CStringGetTextDatum(_("Create Role"));
+ 	if (attributes & ROLE_ATTR_CREATEDB)
+ 		temp_array[i++] = CStringGetTextDatum(_("Create DB"));
+ 	if (attributes & ROLE_ATTR_CATUPDATE)
+ 		temp_array[i++] = CStringGetTextDatum(_("Catalog Update"));
+ 	if (attributes & ROLE_ATTR_CANLOGIN)
+ 		temp_array[i++] = CStringGetTextDatum(_("Login"));
+ 	if (attributes & ROLE_ATTR_REPLICATION)
+ 		temp_array[i++] = CStringGetTextDatum(_("Replication"));
+ 	if (attributes & ROLE_ATTR_BYPASSRLS)
+ 		temp_array[i++] = CStringGetTextDatum(_("Bypass RLS"));
+ 
+ 	result = construct_array(temp_array, i, TEXTOID, -1, false, 'i');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * convert_role_attr_string
+  *		Convert text string to RoleAttr value.
+  */
+ static RoleAttr
+ convert_role_attr_string(text *attr_type_text)
+ {
+ 	char	   *attr_type = text_to_cstring(attr_type_text);
+ 
+ 	if (pg_strcasecmp(attr_type, "SUPERUSER") == 0)
+ 		return ROLE_ATTR_SUPERUSER;
+ 	else if (pg_strcasecmp(attr_type, "INHERIT") == 0)
+ 		return ROLE_ATTR_INHERIT;
+ 	else if (pg_strcasecmp(attr_type, "CREATEROLE") == 0)
+ 		return ROLE_ATTR_CREATEROLE;
+ 	else if (pg_strcasecmp(attr_type, "CREATEDB") == 0)
+ 		return ROLE_ATTR_CREATEDB;
+ 	else if (pg_strcasecmp(attr_type, "CATUPDATE") == 0)
+ 		return ROLE_ATTR_CATUPDATE;
+ 	else if (pg_strcasecmp(attr_type, "CANLOGIN") == 0)
+ 		return ROLE_ATTR_CANLOGIN;
+ 	else if (pg_strcasecmp(attr_type, "REPLICATION") == 0)
+ 		return ROLE_ATTR_REPLICATION;
+ 	else if (pg_strcasecmp(attr_type, "BYPASSRLS") == 0)
+ 		return ROLE_ATTR_BYPASSRLS;
+ 	else
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("unrecognized role attribute: \"%s\"", attr_type)));
+ }
  
  /*
   * initialization function (called by InitPostgres)
*************** RoleMembershipCacheCallback(Datum arg, i
*** 4634,4656 ****
  }
  
  
- /* Check if specified role has rolinherit set */
- static bool
- has_rolinherit(Oid roleid)
- {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
- 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (HeapTupleIsValid(utup))
- 	{
- 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
- 		ReleaseSysCache(utup);
- 	}
- 	return result;
- }
- 
- 
  /*
   * Get a list of roles that the specified roleid has the privileges of
   *
--- 4815,4820 ----
*************** roles_has_privs_of(Oid roleid)
*** 4697,4703 ****
  		int			i;
  
  		/* Ignore non-inheriting roles */
! 		if (!has_rolinherit(memberid))
  			continue;
  
  		/* Find roles that memberid is directly a member of */
--- 4861,4867 ----
  		int			i;
  
  		/* Ignore non-inheriting roles */
! 		if (!has_role_attribute(memberid, ROLE_ATTR_INHERIT))
  			continue;
  
  		/* Find roles that memberid is directly a member of */
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
new file mode 100644
index 5c75390..ccb1066
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
*************** RI_Initial_Check(Trigger *trigger, Relat
*** 2308,2314 ****
  	 * bypassrls right or is the table owner of the table(s) involved which
  	 * have RLS enabled.
  	 */
! 	if (!has_bypassrls_privilege(GetUserId()) &&
  		((pk_rel->rd_rel->relrowsecurity &&
  		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
  		 (fk_rel->rd_rel->relrowsecurity &&
--- 2308,2314 ----
  	 * bypassrls right or is the table owner of the table(s) involved which
  	 * have RLS enabled.
  	 */
! 	if (!have_role_attribute(ROLE_ATTR_BYPASSRLS) &&
  		((pk_rel->rd_rel->relrowsecurity &&
  		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
  		 (fk_rel->rd_rel->relrowsecurity &&
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
new file mode 100644
index 8fccb4c..db2a0fb
*** a/src/backend/utils/init/miscinit.c
--- b/src/backend/utils/init/miscinit.c
***************
*** 40,45 ****
--- 40,46 ----
  #include "storage/pg_shmem.h"
  #include "storage/proc.h"
  #include "storage/procarray.h"
+ #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/guc.h"
  #include "utils/memutils.h"
*************** SetUserIdAndContext(Oid userid, bool sec
*** 329,352 ****
  
  
  /*
-  * Check whether specified role has explicit REPLICATION privilege
-  */
- bool
- has_rolreplication(Oid roleid)
- {
- 	bool		result = false;
- 	HeapTuple	utup;
- 
- 	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- 	if (HeapTupleIsValid(utup))
- 	{
- 		result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication;
- 		ReleaseSysCache(utup);
- 	}
- 	return result;
- }
- 
- /*
   * Initialize user identity during normal backend startup
   */
  void
--- 330,335 ----
*************** InitializeSessionUserId(const char *role
*** 375,381 ****
  	roleid = HeapTupleGetOid(roleTup);
  
  	AuthenticatedUserId = roleid;
! 	AuthenticatedUserIsSuperuser = rform->rolsuper;
  
  	/* This sets OuterUserId/CurrentUserId too */
  	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
--- 358,364 ----
  	roleid = HeapTupleGetOid(roleTup);
  
  	AuthenticatedUserId = roleid;
! 	AuthenticatedUserIsSuperuser = (rform->rolattr & ROLE_ATTR_SUPERUSER);
  
  	/* This sets OuterUserId/CurrentUserId too */
  	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
*************** InitializeSessionUserId(const char *role
*** 394,400 ****
  		/*
  		 * Is role allowed to login at all?
  		 */
! 		if (!rform->rolcanlogin)
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  					 errmsg("role \"%s\" is not permitted to log in",
--- 377,383 ----
  		/*
  		 * Is role allowed to login at all?
  		 */
! 		if (!(rform->rolattr & ROLE_ATTR_CANLOGIN))
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  					 errmsg("role \"%s\" is not permitted to log in",
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
new file mode 100644
index c348034..268001f
*** a/src/backend/utils/init/postinit.c
--- b/src/backend/utils/init/postinit.c
*************** InitPostgres(const char *in_dbname, Oid
*** 762,768 ****
  	{
  		Assert(!bootstrap);
  
! 		if (!superuser() && !has_rolreplication(GetUserId()))
  			ereport(FATAL,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser or replication role to start walsender")));
--- 762,768 ----
  	{
  		Assert(!bootstrap);
  
! 		if (!have_role_attribute(ROLE_ATTR_REPLICATION))
  			ereport(FATAL,
  					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  					 errmsg("must be superuser or replication role to start walsender")));
diff --git a/src/backend/utils/misc/superuser.c b/src/backend/utils/misc/superuser.c
new file mode 100644
index ff0f947..67d070c
*** a/src/backend/utils/misc/superuser.c
--- b/src/backend/utils/misc/superuser.c
*************** superuser_arg(Oid roleid)
*** 58,63 ****
--- 58,64 ----
  {
  	bool		result;
  	HeapTuple	rtup;
+ 	RoleAttr	attributes;
  
  	/* Quick out for cache hit */
  	if (OidIsValid(last_roleid) && last_roleid == roleid)
*************** superuser_arg(Oid roleid)
*** 71,77 ****
  	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(rtup))
  	{
! 		result = ((Form_pg_authid) GETSTRUCT(rtup))->rolsuper;
  		ReleaseSysCache(rtup);
  	}
  	else
--- 72,79 ----
  	rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
  	if (HeapTupleIsValid(rtup))
  	{
! 		attributes = ((Form_pg_authid) GETSTRUCT(rtup))->rolattr;
! 		result = (attributes & ROLE_ATTR_SUPERUSER);
  		ReleaseSysCache(rtup);
  	}
  	else
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
new file mode 100644
index eb633bc..f638167
*** a/src/bin/pg_dump/pg_dumpall.c
--- b/src/bin/pg_dump/pg_dumpall.c
*************** dumpRoles(PGconn *conn)
*** 671,680 ****
  	/* note: rolconfig is dumped later */
  	if (server_version >= 90500)
  		printfPQExpBuffer(buf,
! 						  "SELECT oid, rolname, rolsuper, rolinherit, "
! 						  "rolcreaterole, rolcreatedb, "
! 						  "rolcanlogin, rolconnlimit, rolpassword, "
! 						  "rolvaliduntil, rolreplication, rolbypassrls, "
  			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
  						  "rolname = current_user AS is_current_user "
  						  "FROM pg_authid "
--- 671,686 ----
  	/* note: rolconfig is dumped later */
  	if (server_version >= 90500)
  		printfPQExpBuffer(buf,
! 						  "SELECT oid, rolname, "
! 						  "pg_check_role_attribute(oid, 'SUPERUSER') AS rolsuper, "
! 						  "pg_check_role_attribute(oid, 'INHERIT') AS rolinherit, "
! 						  "pg_check_role_attribute(oid, 'CREATEROLE') AS rolcreaterole, "
! 						  "pg_check_role_attribute(oid, 'CREATEDB') AS rolcreatedb, "
! 						  "pg_check_role_attribute(oid, 'CANLOGIN') AS rolcanlogin, "
! 						  "pg_check_role_attribute(oid, 'REPLICATION') AS rolreplication, "
! 						  "pg_check_role_attribute(oid, 'BYPASSRLS') AS rolbypassrls, "
! 						  "rolconnlimit, rolpassword, "
! 						  "rolvaliduntil, "
  			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
  						  "rolname = current_user AS is_current_user "
  						  "FROM pg_authid "
diff --git a/src/include/catalog/acldefs.h b/src/include/catalog/acldefs.h
new file mode 100644
index ...2dcc174
*** a/src/include/catalog/acldefs.h
--- b/src/include/catalog/acldefs.h
***************
*** 0 ****
--- 1,72 ----
+ /*-------------------------------------------------------------------------
+  *
+  * acldefs.h
+  *	  base definitions for ACLs and role attributes
+  *
+  * Portions Copyright (c) 2014, PostgreSQL Global Development Group
+  *
+  * src/include/catalog/acldefs.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef ACLDEFS_H
+ #define ACLDEFS_H
+ 
+ /*
+  * Grantable rights are encoded so that we can OR them together in a bitmask.
+  * The present representation of AclItem limits us to 16 distinct rights,
+  * even though AclMode is defined as uint32.  See utils/acl.h.
+  *
+  * Caution: changing these codes breaks stored ACLs, hence forces initdb.
+  */
+ typedef uint32 AclMode;			/* a bitmask of privilege bits */
+ 
+ #define ACL_INSERT		(1<<0)	/* for relations */
+ #define ACL_SELECT		(1<<1)
+ #define ACL_UPDATE		(1<<2)
+ #define ACL_DELETE		(1<<3)
+ #define ACL_TRUNCATE	(1<<4)
+ #define ACL_REFERENCES	(1<<5)
+ #define ACL_TRIGGER		(1<<6)
+ #define ACL_EXECUTE		(1<<7)	/* for functions */
+ #define ACL_USAGE		(1<<8)	/* for languages, namespaces, FDWs, and
+ 								 * servers */
+ #define ACL_CREATE		(1<<9)	/* for namespaces and databases */
+ #define ACL_CREATE_TEMP (1<<10) /* for databases */
+ #define ACL_CONNECT		(1<<11) /* for databases */
+ #define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
+ #define ACL_NO_RIGHTS	0
+ /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
+ #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
+ 
+ #define ACL_ID_PUBLIC	0		/* placeholder for id in a PUBLIC acl item */
+ 
+ 
+ /*
+  * Role attributes are encoded so that we can OR them together in a bitmask.
+  * The present representation of RoleAttr (defined in acl.h) limits us to 64
+  * distinct rights.
+  *
+  * Note about ROLE_ATTR_ALL: This symbol is used verbatim by genbki.pl, which
+  * means we need to hard-code its value instead of using a symbolic definition.
+  * Therefore, whenever role attributes are changed, this value MUST be updated
+  * manually.
+  */
+ 
+ /* A bitmask for role attributes */
+ typedef uint64 RoleAttr;
+ 
+ #define ROLE_ATTR_NONE			0
+ #define ROLE_ATTR_SUPERUSER		(1<<0)
+ #define ROLE_ATTR_INHERIT		(1<<1)
+ #define ROLE_ATTR_CREATEROLE	(1<<2)
+ #define ROLE_ATTR_CREATEDB		(1<<3)
+ #define ROLE_ATTR_CATUPDATE		(1<<4)
+ #define ROLE_ATTR_CANLOGIN		(1<<5)
+ #define ROLE_ATTR_REPLICATION	(1<<6)
+ #define ROLE_ATTR_BYPASSRLS		(1<<7)
+ #define N_ROLE_ATTRIBUTES		8		/* 1 plus the last 1<<x */
+ #define ROLE_ATTR_ALL			255		/* (1 << N_ROLE_ATTRIBUTES) - 1 */
+ 
+ 
+ #endif   /* ACLDEFS_H */
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
new file mode 100644
index 3b63d2b..a45f38d
*** a/src/include/catalog/pg_authid.h
--- b/src/include/catalog/pg_authid.h
***************
*** 21,26 ****
--- 21,27 ----
  #ifndef PG_AUTHID_H
  #define PG_AUTHID_H
  
+ #include "catalog/acldefs.h"
  #include "catalog/genbki.h"
  
  /*
***************
*** 45,60 ****
  CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
  {
  	NameData	rolname;		/* name of role */
! 	bool		rolsuper;		/* read this field via superuser() only! */
! 	bool		rolinherit;		/* inherit privileges from other roles? */
! 	bool		rolcreaterole;	/* allowed to create more roles? */
! 	bool		rolcreatedb;	/* allowed to create databases? */
! 	bool		rolcatupdate;	/* allowed to alter catalogs manually? */
! 	bool		rolcanlogin;	/* allowed to log in as session user? */
! 	bool		rolreplication; /* role used for streaming replication */
! 	bool		rolbypassrls;	/* allowed to bypass row level security? */
  	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
- 
  	/* remaining fields may be null; use heap_getattr to read them! */
  	text		rolpassword;	/* password, if any */
  	timestamptz rolvaliduntil;	/* password expiration time, if any */
--- 46,53 ----
  CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MACRO
  {
  	NameData	rolname;		/* name of role */
! 	int64		rolattr;		/* role attribute bitmask */
  	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
  	/* remaining fields may be null; use heap_getattr to read them! */
  	text		rolpassword;	/* password, if any */
  	timestamptz rolvaliduntil;	/* password expiration time, if any */
*************** typedef FormData_pg_authid *Form_pg_auth
*** 74,101 ****
   *		compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid					12
  #define Anum_pg_authid_rolname			1
! #define Anum_pg_authid_rolsuper			2
! #define Anum_pg_authid_rolinherit		3
! #define Anum_pg_authid_rolcreaterole	4
! #define Anum_pg_authid_rolcreatedb		5
! #define Anum_pg_authid_rolcatupdate		6
! #define Anum_pg_authid_rolcanlogin		7
! #define Anum_pg_authid_rolreplication	8
! #define Anum_pg_authid_rolbypassrls		9
! #define Anum_pg_authid_rolconnlimit		10
! #define Anum_pg_authid_rolpassword		11
! #define Anum_pg_authid_rolvaliduntil	12
  
  /* ----------------
   *		initial contents of pg_authid
   *
   * The uppercase quantities will be replaced at initdb time with
   * user choices.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t -1 _null_ _null_));
  
  #define BOOTSTRAP_SUPERUSERID 10
  
--- 67,91 ----
   *		compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid					5
  #define Anum_pg_authid_rolname			1
! #define Anum_pg_authid_rolattr			2
! #define Anum_pg_authid_rolconnlimit		3
! #define Anum_pg_authid_rolpassword		4
! #define Anum_pg_authid_rolvaliduntil	5
! 
  
  /* ----------------
   *		initial contents of pg_authid
   *
   * The uppercase quantities will be replaced at initdb time with
   * user choices.
+  *
+  * PGROLATTRALL is substituted by genbki.pl to use the value defined by
+  * ROLE_ATTR_ALL.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" PGROLATTRALL -1 _null_ _null_));
  
  #define BOOTSTRAP_SUPERUSERID 10
  
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index f766ed7..7f64aaa
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("rank of hypothetical row without
*** 5136,5141 ****
--- 5136,5154 ----
  DATA(insert OID = 3993 ( dense_rank_final	PGNSP PGUID 12 1 0 2276 0 f f f f f f i 2 0 20 "2281 2276" "{2281,2276}" "{i,v}" _null_ _null_	hypothetical_dense_rank_final _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  
+ /* role attribute support functions */
+ DATA(insert OID = 3994 ( pg_has_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ pg_has_role_attribute_id _null_ _null_ _null_ ));
+ DESCR("check role attribute by role oid with superuser bypass check");
+ DATA(insert OID = 3995 ( pg_has_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "19 25" _null_ _null_ _null_ _null_ pg_has_role_attribute_name _null_ _null_ _null_ ));
+ DESCR("check role attribute by role name with superuser bypass check");
+ DATA(insert OID = 3996 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_id _null_ _null_ _null_ ));
+ DESCR("check role attribute by role id");
+ DATA(insert OID = 3997 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "19 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_name _null_ _null_ _null_ ));
+ DESCR("check role attribute by role name");
+ DATA(insert OID = 3998 ( pg_check_role_attribute		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "20 25" _null_ _null_ _null_ _null_ pg_check_role_attribute_attrs _null_ _null_ _null_ ));
+ DESCR("check role attribute");
+ DATA(insert OID = 3999 ( pg_all_role_attributes		PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 1009 "20" _null_ _null_ _null_ _null_ pg_all_role_attributes _null_ _null_ _null_));
+ DESCR("convert role attributes to string array");
  
  /*
   * Symbolic values for provolatile column: these indicate whether the result
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 458eeb0..1b97728
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 23,28 ****
--- 23,29 ----
  #include "nodes/bitmapset.h"
  #include "nodes/primnodes.h"
  #include "nodes/value.h"
+ #include "catalog/acldefs.h"
  #include "utils/lockwaitpolicy.h"
  
  /* Possible sources of a Query */
*************** typedef enum SortByNulls
*** 51,83 ****
  	SORTBY_NULLS_LAST
  } SortByNulls;
  
- /*
-  * Grantable rights are encoded so that we can OR them together in a bitmask.
-  * The present representation of AclItem limits us to 16 distinct rights,
-  * even though AclMode is defined as uint32.  See utils/acl.h.
-  *
-  * Caution: changing these codes breaks stored ACLs, hence forces initdb.
-  */
- typedef uint32 AclMode;			/* a bitmask of privilege bits */
- 
- #define ACL_INSERT		(1<<0)	/* for relations */
- #define ACL_SELECT		(1<<1)
- #define ACL_UPDATE		(1<<2)
- #define ACL_DELETE		(1<<3)
- #define ACL_TRUNCATE	(1<<4)
- #define ACL_REFERENCES	(1<<5)
- #define ACL_TRIGGER		(1<<6)
- #define ACL_EXECUTE		(1<<7)	/* for functions */
- #define ACL_USAGE		(1<<8)	/* for languages, namespaces, FDWs, and
- 								 * servers */
- #define ACL_CREATE		(1<<9)	/* for namespaces and databases */
- #define ACL_CREATE_TEMP (1<<10) /* for databases */
- #define ACL_CONNECT		(1<<11) /* for databases */
- #define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
- #define ACL_NO_RIGHTS	0
- /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
- #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
- 
  
  /*****************************************************************************
   *	Query Tree
--- 52,57 ----
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
new file mode 100644
index a8e3164..4e8d81c
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
***************
*** 30,42 ****
  
  
  /*
-  * typedef AclMode is declared in parsenodes.h, also the individual privilege
-  * bit meanings are defined there
-  */
- 
- #define ACL_ID_PUBLIC	0		/* placeholder for id in a PUBLIC acl item */
- 
- /*
   * AclItem
   *
   * Note: must be same size on all platforms, because the size is hardcoded
--- 30,35 ----
*************** extern bool pg_foreign_data_wrapper_owne
*** 326,332 ****
  extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
  extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
  extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
! extern bool has_createrole_privilege(Oid roleid);
! extern bool has_bypassrls_privilege(Oid roleid);
  
  #endif   /* ACL_H */
--- 319,328 ----
  extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
  extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
  extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
! 
! /* role attribute check routines */
! extern bool has_role_attribute(Oid roleid, RoleAttr attribute);
! extern bool have_role_attribute(RoleAttr attribute);
! extern bool check_role_attribute(Oid roleid, RoleAttr attribute);
  
  #endif   /* ACL_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index 2da3002..c8e0e3a
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum pg_has_role_id_name(PG_FUNC
*** 106,111 ****
--- 106,117 ----
  extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS);
  extern Datum pg_has_role_name(PG_FUNCTION_ARGS);
  extern Datum pg_has_role_id(PG_FUNCTION_ARGS);
+ extern Datum pg_has_role_attribute_id(PG_FUNCTION_ARGS);
+ extern Datum pg_has_role_attribute_name(PG_FUNCTION_ARGS);
+ extern Datum pg_check_role_attribute_id(PG_FUNCTION_ARGS);
+ extern Datum pg_check_role_attribute_name(PG_FUNCTION_ARGS);
+ extern Datum pg_check_role_attribute_attrs(PG_FUNCTION_ARGS);
+ extern Datum pg_all_role_attributes(PG_FUNCTION_ARGS);
  
  /* bool.c */
  extern Datum boolin(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
new file mode 100644
index 80c3351..d105215
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
*************** pg_group| SELECT pg_authid.rolname AS gr
*** 1314,1320 ****
             FROM pg_auth_members
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
!   WHERE (NOT pg_authid.rolcanlogin);
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,
--- 1314,1320 ----
             FROM pg_auth_members
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
!   WHERE (NOT pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN'::text));
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,
*************** pg_replication_slots| SELECT l.slot_name
*** 1405,1421 ****
     FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, xmin, catalog_xmin, restart_lsn)
       LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
  pg_roles| SELECT pg_authid.rolname,
!     pg_authid.rolsuper,
!     pg_authid.rolinherit,
!     pg_authid.rolcreaterole,
!     pg_authid.rolcreatedb,
!     pg_authid.rolcatupdate,
!     pg_authid.rolcanlogin,
!     pg_authid.rolreplication,
      pg_authid.rolconnlimit,
      '********'::text AS rolpassword,
      pg_authid.rolvaliduntil,
-     pg_authid.rolbypassrls,
      s.setconfig AS rolconfig,
      pg_authid.oid
     FROM (pg_authid
--- 1405,1421 ----
     FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, xmin, catalog_xmin, restart_lsn)
       LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
  pg_roles| SELECT pg_authid.rolname,
!     pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER'::text) AS rolsuper,
!     pg_check_role_attribute(pg_authid.rolattr, 'INHERIT'::text) AS rolinherit,
!     pg_check_role_attribute(pg_authid.rolattr, 'CREATEROLE'::text) AS rolcreaterole,
!     pg_check_role_attribute(pg_authid.rolattr, 'CREATEDB'::text) AS rolcreatedb,
!     pg_check_role_attribute(pg_authid.rolattr, 'CATUPDATE'::text) AS rolcatupdate,
!     pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN'::text) AS rolcanlogin,
!     pg_check_role_attribute(pg_authid.rolattr, 'REPLICATION'::text) AS rolreplication,
!     pg_check_role_attribute(pg_authid.rolattr, 'BYPASSRLS'::text) AS rolbypassrls,
      pg_authid.rolconnlimit,
      '********'::text AS rolpassword,
      pg_authid.rolvaliduntil,
      s.setconfig AS rolconfig,
      pg_authid.oid
     FROM (pg_authid
*************** pg_settings| SELECT a.name,
*** 1608,1623 ****
     FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline);
  pg_shadow| SELECT pg_authid.rolname AS usename,
      pg_authid.oid AS usesysid,
!     pg_authid.rolcreatedb AS usecreatedb,
!     pg_authid.rolsuper AS usesuper,
!     pg_authid.rolcatupdate AS usecatupd,
!     pg_authid.rolreplication AS userepl,
      pg_authid.rolpassword AS passwd,
      (pg_authid.rolvaliduntil)::abstime AS valuntil,
      s.setconfig AS useconfig
     FROM (pg_authid
       LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
!   WHERE pg_authid.rolcanlogin;
  pg_stat_activity| SELECT s.datid,
      d.datname,
      s.pid,
--- 1608,1623 ----
     FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline);
  pg_shadow| SELECT pg_authid.rolname AS usename,
      pg_authid.oid AS usesysid,
!     pg_check_role_attribute(pg_authid.rolattr, 'CREATEDB'::text) AS usecreatedb,
!     pg_check_role_attribute(pg_authid.rolattr, 'SUPERUSER'::text) AS usesuper,
!     pg_check_role_attribute(pg_authid.rolattr, 'CATUPDATE'::text) AS usecatupd,
!     pg_check_role_attribute(pg_authid.rolattr, 'REPLICATION'::text) AS userepl,
      pg_authid.rolpassword AS passwd,
      (pg_authid.rolvaliduntil)::abstime AS valuntil,
      s.setconfig AS useconfig
     FROM (pg_authid
       LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
!   WHERE pg_check_role_attribute(pg_authid.rolattr, 'CANLOGIN'::text);
  pg_stat_activity| SELECT s.datid,
      d.datname,
      s.pid,
#36Stephen Frost
sfrost@snowman.net
In reply to: Adam Brightwell (#35)
Re: Role Attribute Bitmask Catalog Representation

Hey Alvaro,

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

I have attached an updated patch for review
(role-attribute-bitmask-v7.patch).

This patch incorporates the 'v5a' patch proposed by Alvaro, input
validation (Assert) check in 'check_role_attribute' and the documentation
updates requested by Stephen.

Not sure if you were looking for me to comment on this or not, but I did
look over the updated docs and the added Asserts and they look good to
me.

Thanks!

Stephen

#37Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#36)
Re: Role Attribute Bitmask Catalog Representation

Stephen Frost wrote:

Hey Alvaro,

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

I have attached an updated patch for review
(role-attribute-bitmask-v7.patch).

This patch incorporates the 'v5a' patch proposed by Alvaro, input
validation (Assert) check in 'check_role_attribute' and the documentation
updates requested by Stephen.

Not sure if you were looking for me to comment on this or not, but I did
look over the updated docs and the added Asserts and they look good to
me.

Okay, great. I will be looking into committing this patch shortly --
unless you want to do it yourself?

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#37)
Re: Role Attribute Bitmask Catalog Representation

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Stephen Frost wrote:

* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:

I have attached an updated patch for review
(role-attribute-bitmask-v7.patch).

This patch incorporates the 'v5a' patch proposed by Alvaro, input
validation (Assert) check in 'check_role_attribute' and the documentation
updates requested by Stephen.

Not sure if you were looking for me to comment on this or not, but I did
look over the updated docs and the added Asserts and they look good to
me.

Okay, great. I will be looking into committing this patch shortly --
unless you want to do it yourself?

Please feel free to go ahead.

Thanks!

Stephen

#39Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Adam Brightwell (#35)
Re: Role Attribute Bitmask Catalog Representation

Adam Brightwell wrote:

Alvaro and Stephen,

I propose this patch on top of Adam's v5. Also included is a full patch

against master.

I have attached an updated patch for review
(role-attribute-bitmask-v7.patch).

This patch incorporates the 'v5a' patch proposed by Alvaro, input
validation (Assert) check in 'check_role_attribute' and the documentation
updates requested by Stephen.

Pushed with a couple of small changes (Catalog.pm complained about the
lack of a CATALOG() line in the new acldefs.h file; you had
pg_role_all_attributes as returning "bool" in the table, but it returns
text[]; and I added index entries for the new functions.)

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#40Adam Brightwell
adam.brightwell@crunchydatasolutions.com
In reply to: Alvaro Herrera (#39)
Re: Role Attribute Bitmask Catalog Representation

Alvarao,

Pushed with a couple of small changes (Catalog.pm complained about the
lack of a CATALOG() line in the new acldefs.h file; you had
pg_role_all_attributes as returning "bool" in the table, but it returns
text[]; and I added index entries for the new functions.)

That's fantastic! Thanks, I appreciate it!

-Adam

--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com