From af019fb9aec0274875a10a89c68c8fecb949349f Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 26 Jul 2005 16:38:29 +0000
Subject: [PATCH] Add a role property 'rolinherit' which, when false, denotes
 that the role doesn't automatically inherit the privileges of roles it is a
 member of; for such a role, membership in another role can be exploited only
 by doing explicit SET ROLE.  The default inherit setting is TRUE, so by
 default the behavior doesn't change, but creating a user with NOINHERIT gives
 closer adherence to our current reading of SQL99.  Documentation still
 lacking, and I think the information schema needs another look.

---
 doc/src/sgml/catalogs.sgml           |  30 +++-
 doc/src/sgml/func.sgml               |   9 +-
 src/backend/catalog/aclchk.c         |  20 +--
 src/backend/catalog/system_views.sql |   6 +-
 src/backend/commands/user.c          |  54 ++++++-
 src/backend/nodes/copyfuncs.c        |   3 +-
 src/backend/nodes/equalfuncs.c       |   3 +-
 src/backend/parser/gram.y            |  25 +++-
 src/backend/parser/keywords.c        |   4 +-
 src/backend/utils/adt/acl.c          | 203 ++++++++++++++++++++++-----
 src/include/catalog/catversion.h     |   4 +-
 src/include/catalog/pg_authid.h      |  22 +--
 src/include/nodes/parsenodes.h       |  15 +-
 src/include/utils/acl.h              |   3 +-
 src/test/regress/expected/rules.out  |   2 +-
 15 files changed, 325 insertions(+), 78 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fa0abca950c..0c8ed68195d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1,6 +1,6 @@
 <!--
  Documentation of the system catalogs, directed toward PostgreSQL developers
- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.108 2005/07/14 05:13:38 tgl Exp $
+ $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.109 2005/07/26 16:38:25 tgl Exp $
  -->
 
 <chapter id="catalogs">
@@ -976,6 +976,14 @@
       <entry>Role has superuser privileges</entry>
      </row>
 
+     <row>
+      <entry><structfield>rolinherit</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></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>
@@ -4728,6 +4736,11 @@
    that blanks out the password field.
   </para>
 
+  <para>
+   This view explicitly exposes the OID column of the underlying table,
+   since that is needed to do joins to other catalogs.
+  </para>
+
   <table>
    <title><structname>pg_roles</> Columns</title>
 
@@ -4756,6 +4769,14 @@
       <entry>Role has superuser privileges</entry>
      </row>
 
+     <row>
+      <entry><structfield>rolinherit</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></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>
@@ -4811,6 +4832,13 @@
       <entry></entry>
       <entry>Session defaults for run-time configuration variables</entry>
      </row>
+
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.oid</literal></entry>
+      <entry>ID of role</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b5ce30105bc..0fdcb1d0df1 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.271 2005/07/26 00:04:17 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.272 2005/07/26 16:38:25 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -8559,7 +8559,12 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
     can access a role in a particular way.  The possibilities for its
     arguments are analogous to <function>has_table_privilege</function>.
     The desired access privilege type must evaluate to
-    <literal>MEMBER</literal>.
+    <literal>MEMBER</literal> or
+    <literal>USAGE</literal>.
+    <literal>MEMBER</literal> denotes direct or indirect membership in
+    the role (that is, the right to do <literal>SET ROLE</>), while
+    <literal>USAGE</literal> denotes whether the privileges of the role
+    are immediately available without doing <literal>SET ROLE</>.
    </para>
 
    <para>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 8053ca73bbf..9e4b58e1298 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.115 2005/07/07 20:39:57 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.116 2005/07/26 16:38:26 tgl Exp $
  *
  * NOTES
  *	  See acl.h.
@@ -1984,7 +1984,7 @@ pg_class_ownercheck(Oid class_oid, Oid roleid)
 
 	ReleaseSysCache(tuple);
 
-	return is_member_of_role(roleid, ownerId);
+	return has_privs_of_role(roleid, ownerId);
 }
 
 /*
@@ -2012,7 +2012,7 @@ pg_type_ownercheck(Oid type_oid, Oid roleid)
 
 	ReleaseSysCache(tuple);
 
-	return is_member_of_role(roleid, ownerId);
+	return has_privs_of_role(roleid, ownerId);
 }
 
 /*
@@ -2040,7 +2040,7 @@ pg_oper_ownercheck(Oid oper_oid, Oid roleid)
 
 	ReleaseSysCache(tuple);
 
-	return is_member_of_role(roleid, ownerId);
+	return has_privs_of_role(roleid, ownerId);
 }
 
 /*
@@ -2068,7 +2068,7 @@ pg_proc_ownercheck(Oid proc_oid, Oid roleid)
 
 	ReleaseSysCache(tuple);
 
-	return is_member_of_role(roleid, ownerId);
+	return has_privs_of_role(roleid, ownerId);
 }
 
 /*
@@ -2096,7 +2096,7 @@ pg_namespace_ownercheck(Oid nsp_oid, Oid roleid)
 
 	ReleaseSysCache(tuple);
 
-	return is_member_of_role(roleid, ownerId);
+	return has_privs_of_role(roleid, ownerId);
 }
 
 /*
@@ -2135,7 +2135,7 @@ pg_tablespace_ownercheck(Oid spc_oid, Oid roleid)
 	heap_endscan(scan);
 	heap_close(pg_tablespace, AccessShareLock);
 
-	return is_member_of_role(roleid, spcowner);
+	return has_privs_of_role(roleid, spcowner);
 }
 
 /*
@@ -2164,7 +2164,7 @@ pg_opclass_ownercheck(Oid opc_oid, Oid roleid)
 
 	ReleaseSysCache(tuple);
 
-	return is_member_of_role(roleid, ownerId);
+	return has_privs_of_role(roleid, ownerId);
 }
 
 /*
@@ -2203,7 +2203,7 @@ pg_database_ownercheck(Oid db_oid, Oid roleid)
 	heap_endscan(scan);
 	heap_close(pg_database, AccessShareLock);
 
-	return is_member_of_role(roleid, dba);
+	return has_privs_of_role(roleid, dba);
 }
 
 /*
@@ -2231,5 +2231,5 @@ pg_conversion_ownercheck(Oid conv_oid, Oid roleid)
 
 	ReleaseSysCache(tuple);
 
-	return is_member_of_role(roleid, ownerId);
+	return has_privs_of_role(roleid, ownerId);
 }
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d20a3b6d7f2..22e2c911107 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -3,20 +3,22 @@
  *
  * Copyright (c) 1996-2005, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.16 2005/06/28 05:08:52 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.17 2005/07/26 16:38:26 tgl Exp $
  */
 
 CREATE VIEW pg_roles AS 
     SELECT 
         rolname,
         rolsuper,
+        rolinherit,
         rolcreaterole,
         rolcreatedb,
         rolcatupdate,
         rolcanlogin,
         '********'::text as rolpassword,
         rolvaliduntil,
-        rolconfig
+        rolconfig,
+        oid
     FROM pg_authid;
 
 CREATE VIEW pg_shadow AS
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 5f8eeae30df..493a6bf7904 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.157 2005/07/25 22:12:31 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.158 2005/07/26 16:38:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -82,6 +82,7 @@ CreateRole(CreateRoleStmt *stmt)
 	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? */
@@ -91,6 +92,7 @@ CreateRole(CreateRoleStmt *stmt)
 	char	   *validUntil = NULL;		/* time the login is valid until */
 	DefElem    *dpassword = NULL;
 	DefElem    *dissuper = NULL;
+	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
 	DefElem    *dcreatedb = NULL;
 	DefElem    *dcanlogin = NULL;
@@ -99,6 +101,19 @@ CreateRole(CreateRoleStmt *stmt)
 	DefElem    *dadminmembers = NULL;
 	DefElem    *dvalidUntil = 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:
+			break;
+	}
+
 	/* Extract options from the statement node tree */
 	foreach(option, stmt->options)
 	{
@@ -120,7 +135,7 @@ CreateRole(CreateRoleStmt *stmt)
 		}
 		else if (strcmp(defel->defname, "sysid") == 0)
 		{
-			ereport(WARNING,
+			ereport(NOTICE,
 					(errmsg("SYSID can no longer be specified")));
 		}
 		else if (strcmp(defel->defname, "superuser") == 0)
@@ -131,6 +146,14 @@ CreateRole(CreateRoleStmt *stmt)
 						 errmsg("conflicting or redundant options")));
 			dissuper = defel;
 		}
+		else if (strcmp(defel->defname, "inherit") == 0)
+		{
+			if (dinherit)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			dinherit = defel;
+		}
 		else if (strcmp(defel->defname, "createrole") == 0)
 		{
 			if (dcreaterole)
@@ -196,6 +219,8 @@ CreateRole(CreateRoleStmt *stmt)
 		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)
@@ -261,6 +286,7 @@ CreateRole(CreateRoleStmt *stmt)
 		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 */
@@ -367,6 +393,7 @@ AlterRole(AlterRoleStmt *stmt)
 	bool		encrypt_password = Password_encryption; /* encrypt password? */
 	char		encrypted_password[MD5_PASSWD_LEN + 1];
 	int			issuper = -1;			/* Make the user a superuser? */
+	int			inherit = -1;			/* Auto inherit privileges? */
 	int			createrole = -1;		/* Can this user create roles? */
 	int			createdb = -1;			/* Can the user create databases? */
 	int			canlogin = -1;			/* Can this user login? */
@@ -374,6 +401,7 @@ AlterRole(AlterRoleStmt *stmt)
 	char	   *validUntil = NULL;		/* time the login is valid until */
 	DefElem    *dpassword = NULL;
 	DefElem    *dissuper = NULL;
+	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
 	DefElem    *dcreatedb = NULL;
 	DefElem    *dcanlogin = NULL;
@@ -408,6 +436,14 @@ AlterRole(AlterRoleStmt *stmt)
 						 errmsg("conflicting or redundant options")));
 			dissuper = defel;
 		}
+		else if (strcmp(defel->defname, "inherit") == 0)
+		{
+			if (dinherit)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			dinherit = defel;
+		}
 		else if (strcmp(defel->defname, "createrole") == 0)
 		{
 			if (dcreaterole)
@@ -458,6 +494,8 @@ AlterRole(AlterRoleStmt *stmt)
 		password = strVal(dpassword->arg);
 	if (dissuper)
 		issuper = intVal(dissuper->arg);
+	if (dinherit)
+		inherit = intVal(dinherit->arg);
 	if (dcreaterole)
 		createrole = intVal(dcreaterole->arg);
 	if (dcreatedb)
@@ -497,10 +535,10 @@ AlterRole(AlterRoleStmt *stmt)
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("must be superuser to alter superusers")));
 	}
-	else
+	else if (!have_createrole_privilege())
 	{
-		if (!have_createrole_privilege() &&
-			!(createrole < 0 &&
+		if (!(inherit < 0 &&
+			  createrole < 0 &&
 			  createdb < 0 &&
 			  canlogin < 0 &&
 			  !rolemembers &&
@@ -536,6 +574,12 @@ AlterRole(AlterRoleStmt *stmt)
 		new_record_repl[Anum_pg_authid_rolcatupdate - 1] = 'r';
 	}
 
+	if (inherit >= 0)
+	{
+		new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
+		new_record_repl[Anum_pg_authid_rolinherit - 1] = 'r';
+	}
+
 	if (createrole >= 0)
 	{
 		new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 8d42cead085..283cf549514 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.311 2005/07/02 23:00:39 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.312 2005/07/26 16:38:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2392,6 +2392,7 @@ _copyCreateRoleStmt(CreateRoleStmt *from)
 {
 	CreateRoleStmt *newnode = makeNode(CreateRoleStmt);
 
+	COPY_SCALAR_FIELD(stmt_type);
 	COPY_STRING_FIELD(role);
 	COPY_NODE_FIELD(options);
 
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e8c8a0cbc94..47e989dbc7d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.248 2005/07/02 23:00:39 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.249 2005/07/26 16:38:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1308,6 +1308,7 @@ _equalDropPLangStmt(DropPLangStmt *a, DropPLangStmt *b)
 static bool
 _equalCreateRoleStmt(CreateRoleStmt *a, CreateRoleStmt *b)
 {
+	COMPARE_SCALAR_FIELD(stmt_type);
 	COMPARE_STRING_FIELD(role);
 	COMPARE_NODE_FIELD(options);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3730068915f..4d21d4eb9f5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.502 2005/07/25 22:12:32 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.503 2005/07/26 16:38:27 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -361,7 +361,7 @@ static void doNegateFloat(Value *v);
 	HANDLER HAVING HEADER HOLD HOUR_P
 
 	ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT
-	INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P
+	INDEX INHERIT INHERITS INITIALLY INNER_P INOUT INPUT_P
 	INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT
 	INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -376,8 +376,8 @@ static void doNegateFloat(Value *v);
 	MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
 
 	NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
-	NOCREATEROLE NOCREATEUSER NOLOGIN_P NONE NOSUPERUSER NOT NOTHING NOTIFY
-	NOTNULL NOWAIT NULL_P NULLIF NUMERIC
+	NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
+	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NUMERIC
 
 	OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OR
 	ORDER OUT_P OUTER_P OVERLAPS OVERLAY OWNER
@@ -581,6 +581,7 @@ CreateRoleStmt:
 			CREATE ROLE RoleId opt_with OptRoleList
 				{
 					CreateRoleStmt *n = makeNode(CreateRoleStmt);
+					n->stmt_type = ROLESTMT_ROLE;
 					n->role = $3;
 					n->options = $5;
 					$$ = (Node *)n;
@@ -630,6 +631,14 @@ OptRoleElem:
 				{
 					$$ = makeDefElem("superuser", (Node *)makeInteger(FALSE));
 				}
+			| INHERIT
+				{
+					$$ = makeDefElem("inherit", (Node *)makeInteger(TRUE));
+				}
+			| NOINHERIT
+				{
+					$$ = makeDefElem("inherit", (Node *)makeInteger(FALSE));
+				}
 			| CREATEDB
 				{
 					$$ = makeDefElem("createdb", (Node *)makeInteger(TRUE));
@@ -700,10 +709,9 @@ CreateUserStmt:
 			CREATE USER RoleId opt_with OptRoleList
 				{
 					CreateRoleStmt *n = makeNode(CreateRoleStmt);
+					n->stmt_type = ROLESTMT_USER;
 					n->role = $3;
-					n->options = lappend($5,
-										 makeDefElem("canlogin",
-													 (Node *)makeInteger(TRUE)));
+					n->options = $5;
 					$$ = (Node *)n;
 				}
 		;
@@ -829,6 +837,7 @@ CreateGroupStmt:
 			CREATE GROUP_P RoleId opt_with OptRoleList
 				{
 					CreateRoleStmt *n = makeNode(CreateRoleStmt);
+					n->stmt_type = ROLESTMT_GROUP;
 					n->role = $3;
 					n->options = $5;
 					$$ = (Node *)n;
@@ -7996,6 +8005,7 @@ unreserved_keyword:
 			| INCLUDING
 			| INCREMENT
 			| INDEX
+			| INHERIT
 			| INHERITS
 			| INPUT_P
 			| INSENSITIVE
@@ -8028,6 +8038,7 @@ unreserved_keyword:
 			| NOCREATEDB
 			| NOCREATEROLE
 			| NOCREATEUSER
+			| NOINHERIT
 			| NOLOGIN_P
 			| NOSUPERUSER
 			| NOTHING
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index 726e7fc01e3..5d4cab2124f 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.162 2005/06/29 20:34:14 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.163 2005/07/26 16:38:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -165,6 +165,7 @@ static const ScanKeyword ScanKeywords[] = {
 	{"including", INCLUDING},
 	{"increment", INCREMENT},
 	{"index", INDEX},
+	{"inherit", INHERIT},
 	{"inherits", INHERITS},
 	{"initially", INITIALLY},
 	{"inner", INNER_P},
@@ -219,6 +220,7 @@ static const ScanKeyword ScanKeywords[] = {
 	{"nocreatedb", NOCREATEDB},
 	{"nocreaterole", NOCREATEROLE},
 	{"nocreateuser", NOCREATEUSER},
+	{"noinherit", NOINHERIT},
 	{"nologin", NOLOGIN_P},
 	{"none", NONE},
 	{"nosuperuser", NOSUPERUSER},
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 7517f2743f9..48a24db1826 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.121 2005/07/26 00:04:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.122 2005/07/26 16:38:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -39,15 +39,29 @@
  * all the roles the "given role" is a member of, directly or indirectly.
  * The cache is flushed whenever we detect a change in pg_auth_members.
  *
+ * There are actually two caches, one computed under "has_privs" rules
+ * (do not recurse where rolinherit isn't true) and one computed under
+ * "is_member" rules (recurse regardless of rolinherit).
+ *
  * Possibly this mechanism should be generalized to allow caching membership
- * info for more than one role?
+ * info for multiple roles?
+ *
+ * The has_privs cache is:
+ * cached_privs_role is the role OID the cache is for.
+ * cached_privs_roles is an OID list of roles that cached_privs_role
+ *		has the privileges of (always including itself).
+ * The cache is valid if cached_privs_role is not InvalidOid.
  *
- * cached_role is the role OID the cache is for.
- * cached_memberships is an OID list of roles that cached_role is a member of.
- * The cache is valid if cached_role is not InvalidOid.
+ * The is_member cache is similarly:
+ * cached_member_role is the role OID the cache is for.
+ * cached_membership_roles is an OID list of roles that cached_member_role
+ *		is a member of (always including itself).
+ * The cache is valid if cached_member_role is not InvalidOid.
  */
-static Oid	cached_role = InvalidOid;
-static List	*cached_memberships = NIL;
+static Oid	cached_privs_role = InvalidOid;
+static List	*cached_privs_roles = NIL;
+static Oid	cached_member_role = InvalidOid;
+static List	*cached_membership_roles = NIL;
 
 
 static const char *getid(const char *s, char *n);
@@ -999,7 +1013,7 @@ aclmask(const Acl *acl, Oid roleid, Oid ownerId,
 	result = 0;
 
 	/* Owner always implicitly has all grant options */
-	if (is_member_of_role(roleid, ownerId))
+	if (has_privs_of_role(roleid, ownerId))
 	{
 		result = mask & ACLITEM_ALL_GOPTION_BITS;
 		if (result == mask)
@@ -1042,7 +1056,7 @@ aclmask(const Acl *acl, Oid roleid, Oid ownerId,
 			continue;			/* already checked it */
 
 		if ((aidata->ai_privs & remaining) &&
-			is_member_of_role(roleid, aidata->ai_grantee))
+			has_privs_of_role(roleid, aidata->ai_grantee))
 		{
 			result |= aidata->ai_privs & mask;
 			if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
@@ -2653,8 +2667,10 @@ pg_has_role_id_id(PG_FUNCTION_ARGS)
  * convert_role_priv_string
  *		Convert text string to AclMode value.
  *
- * There is only one interesting option, MEMBER, which we represent by
- * ACL_USAGE since no formal ACL bit is defined for it.  This convention
+ * We use USAGE to denote whether the privileges of the role are accessible
+ * (has_privs), MEMBER to denote is_member, and MEMBER WITH GRANT OPTION
+ * (or ADMIN OPTION) to denote is_admin.  There is no ACL bit corresponding
+ * to MEMBER so we cheat and use ACL_CREATE for that.  This convention
  * is shared only with pg_role_aclcheck, below.
  */
 static AclMode
@@ -2668,12 +2684,15 @@ convert_role_priv_string(text *priv_type_text)
 	/*
 	 * Return mode from priv_type string
 	 */
-	if (pg_strcasecmp(priv_type, "MEMBER") == 0)
+	if (pg_strcasecmp(priv_type, "USAGE") == 0)
 		return ACL_USAGE;
-	if (pg_strcasecmp(priv_type, "MEMBER WITH GRANT OPTION") == 0)
-		return ACL_GRANT_OPTION_FOR(ACL_USAGE);
-	if (pg_strcasecmp(priv_type, "MEMBER WITH ADMIN OPTION") == 0)
-		return ACL_GRANT_OPTION_FOR(ACL_USAGE);
+	if (pg_strcasecmp(priv_type, "MEMBER") == 0)
+		return ACL_CREATE;
+	if (pg_strcasecmp(priv_type, "USAGE WITH GRANT OPTION") == 0 ||
+		pg_strcasecmp(priv_type, "USAGE WITH ADMIN OPTION") == 0 ||
+		pg_strcasecmp(priv_type, "MEMBER WITH GRANT OPTION") == 0 ||
+		pg_strcasecmp(priv_type, "MEMBER WITH ADMIN OPTION") == 0)
+		return ACL_GRANT_OPTION_FOR(ACL_CREATE);
 
 	ereport(ERROR,
 			(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -2688,20 +2707,22 @@ convert_role_priv_string(text *priv_type_text)
 static AclResult
 pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode)
 {
-	if (mode & ACL_GRANT_OPTION_FOR(ACL_USAGE))
+	if (mode & ACL_GRANT_OPTION_FOR(ACL_CREATE))
 	{
 		if (is_admin_of_role(roleid, role_oid))
 			return ACLCHECK_OK;
-		else
-			return ACLCHECK_NO_PRIV;
 	}
-	else
+	if (mode & ACL_CREATE)
 	{
 		if (is_member_of_role(roleid, role_oid))
 			return ACLCHECK_OK;
-		else
-			return ACLCHECK_NO_PRIV;
 	}
+	if (mode & ACL_USAGE)
+	{
+		if (has_privs_of_role(roleid, role_oid))
+			return ACLCHECK_OK;
+	}
+	return ACLCHECK_NO_PRIV;
 }
 
 
@@ -2730,14 +2751,130 @@ initialize_acl(void)
 static void
 RoleMembershipCacheCallback(Datum arg, Oid relid)
 {
-	/* Force membership cache to be recomputed on next use */
-	cached_role = InvalidOid;
+	/* Force membership caches to be recomputed on next use */
+	cached_privs_role = InvalidOid;
+	cached_member_role = InvalidOid;
+}
+
+
+/* Check if specified role has rolinherit set */
+static bool
+has_rolinherit(Oid roleid)
+{
+	bool		result = false;
+	HeapTuple	utup;
+
+	utup = SearchSysCache(AUTHOID,
+						  ObjectIdGetDatum(roleid),
+						  0, 0, 0);
+	if (HeapTupleIsValid(utup))
+	{
+		result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
+		ReleaseSysCache(utup);
+	}
+	return result;
+}
+
+
+/*
+ * Does member have the privileges of role (directly or indirectly)?
+ *
+ * This is defined not to recurse through roles that don't have rolinherit
+ * set; for such roles, membership implies the ability to do SET ROLE, but
+ * the privileges are not available until you've done so.
+ *
+ * Since indirect membership testing is relatively expensive, we cache
+ * a list of memberships.
+ */
+bool
+has_privs_of_role(Oid member, Oid role)
+{
+	List		*roles_list;
+	ListCell	*l;
+	List		*new_cached_privs_roles;
+	MemoryContext	oldctx;
+
+	/* Fast path for simple case */
+	if (member == role)
+		return true;
+
+	/* Superusers have every privilege, so are part of every role */
+	if (superuser_arg(member))
+		return true;
+
+	/* If cache is already valid, just use the list */
+	if (OidIsValid(cached_privs_role) && cached_privs_role == member)
+		return list_member_oid(cached_privs_roles, role);
+
+	/* 
+	 * Find all the roles that member is a member of,
+	 * including multi-level recursion.  The role itself will always
+	 * be the first element of the resulting list.
+	 *
+	 * Each element of the list is scanned to see if it adds any indirect
+	 * memberships.  We can use a single list as both the record of
+	 * already-found memberships and the agenda of roles yet to be scanned.
+	 * This is a bit tricky but works because the foreach() macro doesn't
+	 * fetch the next list element until the bottom of the loop.
+	 */
+	roles_list = list_make1_oid(member);
+
+	foreach(l, roles_list)
+	{
+		Oid		memberid = lfirst_oid(l);
+		CatCList	*memlist;
+		int 		i;
+
+		/* Ignore non-inheriting roles */
+		if (!has_rolinherit(memberid))
+			continue;
+
+		/* Find roles that memberid is directly a member of */
+		memlist = SearchSysCacheList(AUTHMEMMEMROLE, 1,
+									 ObjectIdGetDatum(memberid),
+									 0, 0, 0);
+		for (i = 0; i < memlist->n_members; i++)
+		{
+			HeapTuple	tup = &memlist->members[i]->tuple;
+			Oid		otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
+
+			/*
+			 * Even though there shouldn't be any loops in the membership
+			 * graph, we must test for having already seen this role.
+			 * It is legal for instance to have both A->B and A->C->B.
+			 */
+			if (!list_member_oid(roles_list, otherid))
+				roles_list = lappend_oid(roles_list, otherid);
+		}
+		ReleaseSysCacheList(memlist);
+	}
+
+	/*
+	 * Copy the completed list into TopMemoryContext so it will persist.
+	 */
+	oldctx = MemoryContextSwitchTo(TopMemoryContext);
+	new_cached_privs_roles = list_copy(roles_list);
+	MemoryContextSwitchTo(oldctx);
+	list_free(roles_list);
+
+	/*
+	 * Now safe to assign to state variable
+	 */
+	cached_privs_role = InvalidOid;	/* just paranoia */
+	list_free(cached_privs_roles);
+	cached_privs_roles = new_cached_privs_roles;
+	cached_privs_role = member;
+
+	/* And now we can return the answer */
+	return list_member_oid(cached_privs_roles, role);
 }
 
 
 /*
  * Is member a member of role (directly or indirectly)?
  *
+ * This is defined to recurse through roles regardless of rolinherit.
+ *
  * Since indirect membership testing is relatively expensive, we cache
  * a list of memberships.
  */
@@ -2746,7 +2883,7 @@ is_member_of_role(Oid member, Oid role)
 {
 	List		*roles_list;
 	ListCell	*l;
-	List		*new_cached_memberships;
+	List		*new_cached_membership_roles;
 	MemoryContext	oldctx;
 
 	/* Fast path for simple case */
@@ -2758,8 +2895,8 @@ is_member_of_role(Oid member, Oid role)
 		return true;
 
 	/* If cache is already valid, just use the list */
-	if (OidIsValid(cached_role) && cached_role == member)
-		return list_member_oid(cached_memberships, role);
+	if (OidIsValid(cached_member_role) && cached_member_role == member)
+		return list_member_oid(cached_membership_roles, role);
 
 	/* 
 	 * Find all the roles that member is a member of,
@@ -2804,20 +2941,20 @@ is_member_of_role(Oid member, Oid role)
 	 * Copy the completed list into TopMemoryContext so it will persist.
 	 */
 	oldctx = MemoryContextSwitchTo(TopMemoryContext);
-	new_cached_memberships = list_copy(roles_list);
+	new_cached_membership_roles = list_copy(roles_list);
 	MemoryContextSwitchTo(oldctx);
 	list_free(roles_list);
 
 	/*
 	 * Now safe to assign to state variable
 	 */
-	cached_role = InvalidOid;	/* just paranoia */
-	list_free(cached_memberships);
-	cached_memberships = new_cached_memberships;
-	cached_role = member;
+	cached_member_role = InvalidOid;	/* just paranoia */
+	list_free(cached_membership_roles);
+	cached_membership_roles = new_cached_membership_roles;
+	cached_member_role = member;
 
 	/* And now we can return the answer */
-	return list_member_oid(cached_memberships, role);
+	return list_member_oid(cached_membership_roles, role);
 }
 
 /*
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 592ea17b115..38f31b114bf 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.291 2005/07/26 00:04:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.292 2005/07/26 16:38:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200507251
+#define CATALOG_VERSION_NO	200507261
 
 #endif
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index 2ea15fea8a1..6672138d865 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_authid.h,v 1.1 2005/06/28 05:09:05 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_authid.h,v 1.2 2005/07/26 16:38:28 tgl Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -44,6 +44,7 @@ CATALOG(pg_authid,1260) BKI_SHARED_RELATION
 {
 	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? */
@@ -69,16 +70,17 @@ typedef FormData_pg_authid *Form_pg_authid;
  *		compiler constants for pg_authid
  * ----------------
  */
-#define Natts_pg_authid					9
+#define Natts_pg_authid					10
 #define Anum_pg_authid_rolname			1
 #define Anum_pg_authid_rolsuper			2
-#define Anum_pg_authid_rolcreaterole	3
-#define Anum_pg_authid_rolcreatedb		4
-#define Anum_pg_authid_rolcatupdate		5
-#define Anum_pg_authid_rolcanlogin		6
-#define Anum_pg_authid_rolpassword		7
-#define Anum_pg_authid_rolvaliduntil	8
-#define Anum_pg_authid_rolconfig		9
+#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_rolpassword		8
+#define Anum_pg_authid_rolvaliduntil	9
+#define Anum_pg_authid_rolconfig		10
 
 /* ----------------
  *		initial contents of pg_authid
@@ -87,7 +89,7 @@ typedef FormData_pg_authid *Form_pg_authid;
  * user choices.
  * ----------------
  */
-DATA(insert OID = 10 ( "POSTGRES" t t t t t _null_ _null_ _null_ ));
+DATA(insert OID = 10 ( "POSTGRES" t t t t t t _null_ _null_ _null_ ));
 
 #define BOOTSTRAP_SUPERUSERID 10
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 32f9b03c585..6d388b07d31 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.285 2005/06/28 19:51:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.286 2005/07/26 16:38:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1139,11 +1139,24 @@ typedef struct DropPLangStmt
 
 /* ----------------------
  *	Create/Alter/Drop Role Statements
+ *
+ * Note: these node types are also used for the backwards-compatible
+ * Create/Alter/Drop User/Group statements.  In the ALTER and DROP cases
+ * there's really no need to distinguish what the original spelling was,
+ * but for CREATE we mark the type because the defaults vary.
  * ----------------------
  */
+typedef enum RoleStmtType
+{
+	ROLESTMT_ROLE,
+	ROLESTMT_USER,
+	ROLESTMT_GROUP
+} RoleStmtType;
+
 typedef struct CreateRoleStmt
 {
 	NodeTag		type;
+	RoleStmtType stmt_type;		/* ROLE/USER/GROUP */
 	char	   *role;			/* role name */
 	List	   *options;		/* List of DefElem nodes */
 } CreateRoleStmt;
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index d3ef0031985..1f216009098 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.82 2005/07/14 21:46:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.83 2005/07/26 16:38:29 tgl Exp $
  *
  * NOTES
  *	  An ACL array is simply an array of AclItems, representing the union
@@ -210,6 +210,7 @@ extern AclMode aclmask(const Acl *acl, Oid roleid, Oid ownerId,
 		AclMode mask, AclMaskHow how);
 extern int aclmembers(const Acl *acl, Oid **roleids);
 
+extern bool has_privs_of_role(Oid member, Oid role);
 extern bool is_member_of_role(Oid member, Oid role);
 extern bool is_admin_of_role(Oid member, Oid role);
 extern void check_is_member_of_role(Oid member, Oid role);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 41cc113b6dd..e204864c3ce 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1281,7 +1281,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
  pg_indexes               | SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, t.spcname AS "tablespace", pg_get_indexdef(i.oid) AS indexdef FROM ((((pg_index x JOIN pg_class c ON ((c.oid = x.indrelid))) JOIN pg_class i ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace))) WHERE ((c.relkind = 'r'::"char") AND (i.relkind = 'i'::"char"));
  pg_locks                 | SELECT l.locktype, l."database", l.relation, l.page, l.tuple, l.transactionid, l.classid, l.objid, l.objsubid, l."transaction", l.pid, l."mode", l."granted" FROM pg_lock_status() l(locktype text, "database" oid, relation oid, page integer, tuple smallint, transactionid xid, classid oid, objid oid, objsubid smallint, "transaction" xid, pid integer, "mode" text, "granted" boolean);
  pg_prepared_xacts        | SELECT p."transaction", p.gid, p."prepared", u.rolname AS "owner", d.datname AS "database" FROM ((pg_prepared_xact() p("transaction" xid, gid text, "prepared" timestamp with time zone, ownerid oid, dbid oid) LEFT JOIN pg_authid u ON ((p.ownerid = u.oid))) LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
- pg_roles                 | SELECT pg_authid.rolname, pg_authid.rolsuper, pg_authid.rolcreaterole, pg_authid.rolcreatedb, pg_authid.rolcatupdate, pg_authid.rolcanlogin, '********'::text AS rolpassword, pg_authid.rolvaliduntil, pg_authid.rolconfig FROM pg_authid;
+ pg_roles                 | SELECT pg_authid.rolname, pg_authid.rolsuper, pg_authid.rolinherit, pg_authid.rolcreaterole, pg_authid.rolcreatedb, pg_authid.rolcatupdate, pg_authid.rolcanlogin, '********'::text AS rolpassword, pg_authid.rolvaliduntil, pg_authid.rolconfig, pg_authid.oid FROM pg_authid;
  pg_rules                 | SELECT n.nspname AS schemaname, c.relname AS tablename, r.rulename, pg_get_ruledef(r.oid) AS definition FROM ((pg_rewrite r JOIN pg_class c ON ((c.oid = r.ev_class))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (r.rulename <> '_RETURN'::name);
  pg_settings              | SELECT a.name, a.setting, a.category, a.short_desc, a.extra_desc, a.context, a.vartype, a.source, a.min_val, a.max_val FROM pg_show_all_settings() a(name text, setting text, category text, short_desc text, extra_desc text, context text, vartype text, source text, min_val text, max_val text);
  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.rolpassword AS passwd, (pg_authid.rolvaliduntil)::abstime AS valuntil, pg_authid.rolconfig AS useconfig FROM pg_authid WHERE pg_authid.rolcanlogin;
-- 
GitLab