Merge pg_shadow && pg_group -- UNTESTED

Started by Stephen Frostalmost 21 years ago17 messages
#1Stephen Frost
sfrost@snowman.net
1 attachment(s)

Greetings,

Here's a proof-of-concept pretty much untested (it compiles) patch
against HEAD for review of the general approach I'm taking to
merging pg_shadow and pg_group. This is in order to support group
ownership and eventually roles. This patch includes my grammar and
get_grosysid move patches, and so conflicts with them.

I'm going to hold off on testing it very much in hopes that I'll get
some feedback on it. A good place to start commenting would probably
be my changes to pg_shadow.h.

Thanks,

Stephen

Attachments:

merge_pg_shadow_group.patchtext/plain; charset=us-asciiDownload
Index: src/backend/access/transam/xact.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/access/transam/xact.c,v
retrieving revision 1.195
diff -u -r1.195 xact.c
--- src/backend/access/transam/xact.c	31 Dec 2004 21:59:29 -0000	1.195
+++ src/backend/access/transam/xact.c	21 Jan 2005 05:43:46 -0000
@@ -1464,7 +1464,7 @@
 	/* NOTIFY commit must come before lower-level cleanup */
 	AtCommit_Notify();
 
-	/* Update the flat password file if we changed pg_shadow or pg_group */
+	/* Update the flat password file if we changed pg_shadow */
 	/* This should be the last step before commit */
 	AtEOXact_UpdatePasswordFile(true);
 
Index: src/backend/catalog/Makefile
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/catalog/Makefile,v
retrieving revision 1.53
diff -u -r1.53 Makefile
--- src/backend/catalog/Makefile	21 Jul 2004 20:34:45 -0000	1.53
+++ src/backend/catalog/Makefile	21 Jan 2005 05:43:46 -0000
@@ -31,7 +31,7 @@
 	pg_operator.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
 	pg_language.h pg_largeobject.h pg_aggregate.h pg_statistic.h \
 	pg_rewrite.h pg_trigger.h pg_listener.h pg_description.h pg_cast.h \
-	pg_namespace.h pg_conversion.h pg_database.h pg_shadow.h pg_group.h \
+	pg_namespace.h pg_conversion.h pg_database.h pg_shadow.h \
 	pg_tablespace.h pg_depend.h indexing.h \
     )
 
Index: src/backend/catalog/aclchk.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/catalog/aclchk.c,v
retrieving revision 1.108
diff -u -r1.108 aclchk.c
--- src/backend/catalog/aclchk.c	31 Dec 2004 21:59:38 -0000	1.108
+++ src/backend/catalog/aclchk.c	21 Jan 2005 05:43:46 -0000
@@ -24,7 +24,6 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
-#include "catalog/pg_group.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
@@ -1209,48 +1208,6 @@
 }
 
 
-AclId
-get_grosysid(char *groname)
-{
-	HeapTuple	tuple;
-	AclId		id = 0;
-
-	tuple = SearchSysCache(GRONAME,
-						   PointerGetDatum(groname),
-						   0, 0, 0);
-	if (HeapTupleIsValid(tuple))
-	{
-		id = ((Form_pg_group) GETSTRUCT(tuple))->grosysid;
-		ReleaseSysCache(tuple);
-	}
-	else
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_OBJECT),
-				 errmsg("group \"%s\" does not exist", groname)));
-	return id;
-}
-
-/*
- * Convert group ID to name, or return NULL if group can't be found
- */
-char *
-get_groname(AclId grosysid)
-{
-	HeapTuple	tuple;
-	char	   *name = NULL;
-
-	tuple = SearchSysCache(GROSYSID,
-						   ObjectIdGetDatum(grosysid),
-						   0, 0, 0);
-	if (HeapTupleIsValid(tuple))
-	{
-		name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tuple))->groname));
-		ReleaseSysCache(tuple);
-	}
-	return name;
-}
-
-
 /*
  * Standardized reporting of aclcheck permissions failures.
  *
@@ -1348,8 +1305,8 @@
 				 AclMode mask, AclMaskHow how)
 {
 	AclMode		result;
-	bool		usesuper,
-				usecatupd;
+	bool		rolesuper,
+				rolecatupd;
 	HeapTuple	tuple;
 	Form_pg_class classForm;
 	Datum		aclDatum;
@@ -1358,7 +1315,7 @@
 	AclId		ownerId;
 
 	/*
-	 * Validate userid, find out if he is superuser, also get usecatupd
+	 * Validate userid, find out if he is superuser, also get rolecatupd
 	 */
 	tuple = SearchSysCache(SHADOWSYSID,
 						   ObjectIdGetDatum(userid),
@@ -1368,11 +1325,11 @@
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("user with ID %u does not exist", userid)));
 
-	usecatupd = ((Form_pg_shadow) GETSTRUCT(tuple))->usecatupd;
+	rolecatupd = ((Form_pg_shadow) GETSTRUCT(tuple))->rolecatupd;
 
 	ReleaseSysCache(tuple);
 
-	usesuper = superuser_arg(userid);
+	rolesuper = superuser_arg(userid);
 
 	/*
 	 * Now get the relation's tuple from pg_class
@@ -1389,7 +1346,7 @@
 
 	/*
 	 * Deny anyone permission to update a system catalog unless
-	 * pg_shadow.usecatupd is set.	(This is to let superusers protect
+	 * pg_shadow.rolecatupd is set.	(This is to let superusers protect
 	 * themselves from themselves.)  Also allow it if
 	 * allowSystemTableMods.
 	 *
@@ -1400,7 +1357,7 @@
 	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE)) &&
 		IsSystemClass(classForm) &&
 		classForm->relkind != RELKIND_VIEW &&
-		!usecatupd &&
+		!rolecatupd &&
 		!allowSystemTableMods)
 	{
 #ifdef ACLDEBUG
@@ -1412,7 +1369,7 @@
 	/*
 	 * Otherwise, superusers bypass all permission-checking.
 	 */
-	if (usesuper)
+	if (rolesuper)
 	{
 #ifdef ACLDEBUG
 		elog(DEBUG2, "%u is superuser, home free", userid);
Index: src/backend/catalog/heap.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/catalog/heap.c,v
retrieving revision 1.279
diff -u -r1.279 heap.c
--- src/backend/catalog/heap.c	10 Jan 2005 20:02:19 -0000	1.279
+++ src/backend/catalog/heap.c	21 Jan 2005 05:43:46 -0000
@@ -252,8 +252,6 @@
 		}
 		else if (strcmp(ShadowRelationName, relname) == 0)
 			relid = RelOid_pg_shadow;
-		else if (strcmp(GroupRelationName, relname) == 0)
-			relid = RelOid_pg_group;
 		else if (strcmp(DatabaseRelationName, relname) == 0)
 			relid = RelOid_pg_database;
 		else if (strcmp(TableSpaceRelationName, relname) == 0)
Index: src/backend/catalog/information_schema.sql
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/catalog/information_schema.sql,v
retrieving revision 1.26
diff -u -r1.26 information_schema.sql
--- src/backend/catalog/information_schema.sql	1 Jan 2005 20:44:14 -0000	1.26
+++ src/backend/catalog/information_schema.sql	21 Jan 2005 05:43:46 -0000
@@ -209,13 +209,13 @@
 
 CREATE VIEW applicable_roles AS
     SELECT CAST(current_user AS sql_identifier) AS grantee,
-           CAST(g.groname AS sql_identifier) AS role_name,
+           CAST(g.rolename AS sql_identifier) AS role_name,
            CAST('NO' AS character_data) AS is_grantable
 
-    FROM pg_group g, pg_user u
+    FROM pg_shadow g, pg_user u
 
-    WHERE u.usesysid = ANY (g.grolist)
-          AND u.usename = current_user;
+    WHERE (g.isgroup IS TRUE AND u.rolesysid = ANY (g.grolist))
+          AND u.rolename = current_user;
 
 GRANT SELECT ON applicable_roles TO PUBLIC;
 
Index: src/backend/catalog/namespace.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/catalog/namespace.c,v
retrieving revision 1.73
diff -u -r1.73 namespace.c
--- src/backend/catalog/namespace.c	31 Dec 2004 21:59:38 -0000	1.73
+++ src/backend/catalog/namespace.c	21 Jan 2005 05:43:47 -0000
@@ -1550,7 +1550,7 @@
 			{
 				char	   *uname;
 
-				uname = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
+				uname = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->rolename);
 				namespaceId = GetSysCacheOid(NAMESPACENAME,
 											 CStringGetDatum(uname),
 											 0, 0, 0);
Index: src/backend/commands/dbcommands.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/commands/dbcommands.c,v
retrieving revision 1.148
diff -u -r1.148 dbcommands.c
--- src/backend/commands/dbcommands.c	31 Dec 2004 21:59:41 -0000	1.148
+++ src/backend/commands/dbcommands.c	21 Jan 2005 05:43:47 -0000
@@ -1017,7 +1017,7 @@
 	if (!HeapTupleIsValid(utup))
 		retval = false;
 	else
-		retval = ((Form_pg_shadow) GETSTRUCT(utup))->usecreatedb;
+		retval = ((Form_pg_shadow) GETSTRUCT(utup))->rolecreatedb;
 
 	ReleaseSysCache(utup);
 
Index: src/backend/commands/user.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/commands/user.c,v
retrieving revision 1.147
diff -u -r1.147 user.c
--- src/backend/commands/user.c	31 Dec 2004 21:59:42 -0000	1.147
+++ src/backend/commands/user.c	21 Jan 2005 05:43:47 -0000
@@ -21,7 +21,6 @@
 #include "catalog/catname.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_database.h"
-#include "catalog/pg_group.h"
 #include "catalog/pg_shadow.h"
 #include "catalog/pg_type.h"
 #include "commands/user.h"
@@ -39,7 +38,6 @@
 
 
 #define PWD_FILE		"pg_pwd"
-#define USER_GROUP_FILE "pg_group"
 
 
 extern bool Password_encryption;
@@ -57,7 +55,6 @@
  * SubTransactionId is detected.
  */
 static SubTransactionId user_file_update_subid = InvalidSubTransactionId;
-static SubTransactionId group_file_update_subid = InvalidSubTransactionId;
 
 #define user_file_update_needed() \
 	do { \
@@ -65,13 +62,6 @@
 			user_file_update_subid = GetCurrentSubTransactionId(); \
 	} while (0)
 
-#define group_file_update_needed() \
-	do { \
-		if (group_file_update_subid == InvalidSubTransactionId) \
-			group_file_update_subid = GetCurrentSubTransactionId(); \
-	} while (0)
-
-
 static void CheckPgUserAclNotNull(void);
 static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
 					  List *members);
@@ -101,26 +91,6 @@
 
 
 /*
- * group_getfilename --- get full pathname of group file
- *
- * Note that result string is palloc'd, and should be freed by the caller.
- */
-char *
-group_getfilename(void)
-{
-	int			bufsize;
-	char	   *pfnam;
-
-	bufsize = strlen(DataDir) + strlen("/global/") +
-		strlen(USER_GROUP_FILE) + 1;
-	pfnam = (char *) palloc(bufsize);
-	snprintf(pfnam, bufsize, "%s/global/%s", DataDir, USER_GROUP_FILE);
-
-	return pfnam;
-}
-
-
-/*
  * Get full pathname of password file.
  *
  * Note that result string is palloc'd, and should be freed by the caller.
@@ -141,153 +111,6 @@
 
 
 /*
- * write_group_file: update the flat group file
- */
-static void
-write_group_file(Relation grel)
-{
-	char	   *filename,
-			   *tempname;
-	int			bufsize;
-	FILE	   *fp;
-	mode_t		oumask;
-	HeapScanDesc scan;
-	HeapTuple	tuple;
-	TupleDesc	dsc = RelationGetDescr(grel);
-
-	/*
-	 * Create a temporary filename to be renamed later.  This prevents the
-	 * backend from clobbering the pg_group file while the postmaster
-	 * might be reading from it.
-	 */
-	filename = group_getfilename();
-	bufsize = strlen(filename) + 12;
-	tempname = (char *) palloc(bufsize);
-	snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
-
-	oumask = umask((mode_t) 077);
-	fp = AllocateFile(tempname, "w");
-	umask(oumask);
-	if (fp == NULL)
-		ereport(ERROR,
-				(errcode_for_file_access(),
-				 errmsg("could not write to temporary file \"%s\": %m", tempname)));
-
-	/*
-	 * Read pg_group and write the file.  Note we use SnapshotSelf to
-	 * ensure we see all effects of current transaction.  (Perhaps could
-	 * do a CommandCounterIncrement beforehand, instead?)
-	 */
-	scan = heap_beginscan(grel, SnapshotSelf, 0, NULL);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
-	{
-		Datum		datum,
-					grolist_datum;
-		bool		isnull;
-		char	   *groname;
-		IdList	   *grolist_p;
-		AclId	   *aidp;
-		int			i,
-					j,
-					num;
-		char	   *usename;
-		bool		first_user = true;
-
-		datum = heap_getattr(tuple, Anum_pg_group_groname, dsc, &isnull);
-		/* ignore NULL groupnames --- shouldn't happen */
-		if (isnull)
-			continue;
-		groname = NameStr(*DatumGetName(datum));
-
-		/*
-		 * Check for invalid characters in the group name.
-		 */
-		i = strcspn(groname, "\n");
-		if (groname[i] != '\0')
-		{
-			ereport(LOG,
-					(errmsg("invalid group name \"%s\"", groname)));
-			continue;
-		}
-
-		grolist_datum = heap_getattr(tuple, Anum_pg_group_grolist, dsc, &isnull);
-		/* Ignore NULL group lists */
-		if (isnull)
-			continue;
-
-		/* be sure the IdList is not toasted */
-		grolist_p = DatumGetIdListP(grolist_datum);
-
-		/* scan grolist */
-		num = IDLIST_NUM(grolist_p);
-		aidp = IDLIST_DAT(grolist_p);
-		for (i = 0; i < num; ++i)
-		{
-			tuple = SearchSysCache(SHADOWSYSID,
-								   PointerGetDatum(aidp[i]),
-								   0, 0, 0);
-			if (HeapTupleIsValid(tuple))
-			{
-				usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
-
-				/*
-				 * Check for illegal characters in the user name.
-				 */
-				j = strcspn(usename, "\n");
-				if (usename[j] != '\0')
-				{
-					ereport(LOG,
-						  (errmsg("invalid user name \"%s\"", usename)));
-					continue;
-				}
-
-				/*
-				 * File format is: "dbname"    "user1" "user2" "user3"
-				 */
-				if (first_user)
-				{
-					fputs_quote(groname, fp);
-					fputs("\t", fp);
-				}
-				else
-					fputs(" ", fp);
-
-				first_user = false;
-				fputs_quote(usename, fp);
-
-				ReleaseSysCache(tuple);
-			}
-		}
-		if (!first_user)
-			fputs("\n", fp);
-		/* if IdList was toasted, free detoasted copy */
-		if ((Pointer) grolist_p != DatumGetPointer(grolist_datum))
-			pfree(grolist_p);
-	}
-	heap_endscan(scan);
-
-	if (FreeFile(fp))
-		ereport(ERROR,
-				(errcode_for_file_access(),
-				 errmsg("could not write to temporary file \"%s\": %m",
-						tempname)));
-
-	/*
-	 * Rename the temp file to its final name, deleting the old pg_pwd. We
-	 * expect that rename(2) is an atomic action.
-	 */
-	if (rename(tempname, filename))
-		ereport(ERROR,
-				(errcode_for_file_access(),
-				 errmsg("could not rename file \"%s\" to \"%s\": %m",
-						tempname, filename)));
-
-	pfree(tempname);
-	pfree(filename);
-}
-
-
-/*
  * write_user_file: update the flat password file
  */
 static void
@@ -328,72 +151,145 @@
 	scan = heap_beginscan(urel, SnapshotSelf, 0, NULL);
 	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
-		Datum		datum;
+		Datum		datum,
+						grolist_datum;
 		bool		isnull;
-		char	   *usename,
-				   *passwd,
-				   *valuntil;
+    bool    isgroup;
+		char		*rolename,
+						*usename,
+						*passwd,
+						*valuntil;
+		IdList	*grolist_p;
+		AclId		*aidp;
 		int			i;
 
-		datum = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &isnull);
-		/* ignore NULL usernames (shouldn't happen) */
-		if (isnull)
-			continue;
-		usename = NameStr(*DatumGetName(datum));
-
-		datum = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &isnull);
-
-		/*
-		 * It can be argued that people having a null password shouldn't
-		 * be allowed to connect under password authentication, because
-		 * they need to have a password set up first. If you think
-		 * assuming an empty password in that case is better, change this
-		 * logic to look something like the code for valuntil.
-		 */
+		datum = heap_getattr(tuple, Anum_pg_shadow_rolename, dsc, &isnull);
+		/* ignore NULL rolenames (shouldn't happen) */
 		if (isnull)
 			continue;
+		rolename = NameStr(*DatumGetName(datum));
 
-		passwd = DatumGetCString(DirectFunctionCall1(textout, datum));
+ 		/*
+ 		 * Check for illegal characters in the rolename.
+ 		 */
+ 		i = strcspn(rolename, "\n");
+ 		if (rolename[i] != '\0')
+ 		{
+ 			ereport(LOG,
+ 					(errmsg("invalid role name \"%s\"", rolename)));
+ 			continue;
+ 		}
 
-		datum = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &isnull);
+		datum = heap_getattr(tuple, Anum_pg_shadow_isgroup, dsc, &isnull);
+		/* ignore NULL isgroup (shouldn't happen) */
 		if (isnull)
-			valuntil = pstrdup("");
-		else
-			valuntil = DatumGetCString(DirectFunctionCall1(abstimeout, datum));
-
-		/*
-		 * Check for illegal characters in the username and password.
-		 */
-		i = strcspn(usename, "\n");
-		if (usename[i] != '\0')
-		{
-			ereport(LOG,
-					(errmsg("invalid user name \"%s\"", usename)));
 			continue;
-		}
-		i = strcspn(passwd, "\n");
-		if (passwd[i] != '\0')
-		{
-			ereport(LOG,
-					(errmsg("invalid user password \"%s\"", passwd)));
-			continue;
-		}
+		isgroup = DatumGetBool(datum);
 
-		/*
-		 * The extra columns we emit here are not really necessary. To
-		 * remove them, the parser in backend/libpq/crypt.c would need to
-		 * be adjusted.
-		 */
-		fputs_quote(usename, fp);
-		fputs(" ", fp);
-		fputs_quote(passwd, fp);
-		fputs(" ", fp);
-		fputs_quote(valuntil, fp);
-		fputs("\n", fp);
+    if (!isgroup) {
+  		datum = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &isnull);
 
-		pfree(passwd);
-		pfree(valuntil);
-	}
+  		/*
+  		 * It can be argued that people having a null password shouldn't
+  		 * be allowed to connect under password authentication, because
+  		 * they need to have a password set up first. If you think
+  		 * assuming an empty password in that case is better, change this
+  		 * logic to look something like the code for valuntil.
+  		 */
+  		if (isnull)
+  			continue;
+  
+  		passwd = DatumGetCString(DirectFunctionCall1(textout, datum));
+  
+  		datum = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &isnull);
+  		if (isnull)
+  			valuntil = pstrdup("");
+  		else
+  			valuntil = DatumGetCString(DirectFunctionCall1(abstimeout, datum));
+  
+   		/*
+  		 * Check for illegal characters in the password.
+  		 */
+  		i = strcspn(passwd, "\n");
+  		if (passwd[i] != '\0')
+  		{
+  			ereport(LOG,
+  					(errmsg("invalid user password \"%s\"", passwd)));
+  			continue;
+  		}
+  
+  		/*
+  		 * The extra columns we emit here are not really necessary. To
+  		 * remove them, the parser in backend/libpq/crypt.c would need to
+  		 * be adjusted.
+  		 */
+  		fputs_quote(rolename, fp);
+  		fputs(" ", fp);
+  		fputs_quote(passwd, fp);
+  		fputs(" ", fp);
+  		fputs_quote(valuntil, fp);
+  		fputs("\n", fp);
+  
+  		pfree(passwd);
+  		pfree(valuntil);
+  	} else {
+      int num, j;
+      bool first_user = true;
+
+			grolist_datum = heap_getattr(tuple, Anum_pg_shadow_grolist, dsc, &isnull);
+			/* Ignore NULL group lists */
+			if (isnull)
+				continue;
+
+			/* be sure the IdList is not toasted */
+			grolist_p = DatumGetIdListP(grolist_datum);
+
+			/* scan grolist */
+			 num = IDLIST_NUM(grolist_p);
+			 aidp = IDLIST_DAT(grolist_p);
+			 for (i = 0; i < num; ++i)
+			 {
+				  tuple = SearchSysCache(SHADOWSYSID,
+											PointerGetDatum(aidp[i]),
+											0, 0, 0);
+					if (HeapTupleIsValid(tuple))
+					{
+						usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->rolename);
+						/*
+						 * Check for illegal characters in the user name.
+						 */
+						j = strcspn(usename, "\n");
+						if (usename[j] != '\0')
+						{
+							ereport(LOG,
+									(errmsg("invalid user name \"%s\"", usename)));
+							continue;
+						}
+
+						/*
+						 * File format is: +"groname"    "user1" "user2" "user3"
+						 */
+						if (first_user)
+						{
+							fputs("+", fp);
+							fputs_quote(rolename, fp);
+							fputs("\t", fp);
+						} else
+							fputs(" ", fp);
+
+						first_user = false;
+						fputs_quote(usename, fp);
+
+						ReleaseSysCache(tuple);
+					}
+			 }
+			 if (!first_user)
+				 fputs("\n", fp);
+			 /* if IdList was toasted, free detoasted copy */
+			 if ((Pointer) grolist_p != DatumGetPointer(grolist_datum))
+				 pfree(grolist_p);
+    }
+  }
 	heap_endscan(scan);
 
 	if (FreeFile(fp))
@@ -418,7 +314,7 @@
 
 
 /*
- * This trigger is fired whenever someone modifies pg_shadow or pg_group
+ * This trigger is fired whenever someone modifies pg_shadow
  * via general-purpose INSERT/UPDATE/DELETE commands.
  *
  * XXX should probably have two separate triggers.
@@ -427,7 +323,6 @@
 update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS)
 {
 	user_file_update_needed();
-	group_file_update_needed();
 
 	return PointerGetDatum(NULL);
 }
@@ -451,16 +346,13 @@
 AtEOXact_UpdatePasswordFile(bool isCommit)
 {
 	Relation	urel = NULL;
-	Relation	grel = NULL;
 
-	if (user_file_update_subid == InvalidSubTransactionId &&
-		group_file_update_subid == InvalidSubTransactionId)
+	if (user_file_update_subid == InvalidSubTransactionId)
 		return;
 
 	if (!isCommit)
 	{
 		user_file_update_subid = InvalidSubTransactionId;
-		group_file_update_subid = InvalidSubTransactionId;
 		return;
 	}
 
@@ -474,10 +366,8 @@
 	 */
 	if (user_file_update_subid != InvalidSubTransactionId)
 		urel = heap_openr(ShadowRelationName, ExclusiveLock);
-	if (group_file_update_subid != InvalidSubTransactionId)
-		grel = heap_openr(GroupRelationName, ExclusiveLock);
 
-	/* Okay to write the files */
+	/* Okay to write the file */
 	if (user_file_update_subid != InvalidSubTransactionId)
 	{
 		user_file_update_subid = InvalidSubTransactionId;
@@ -485,13 +375,6 @@
 		heap_close(urel, NoLock);
 	}
 
-	if (group_file_update_subid != InvalidSubTransactionId)
-	{
-		group_file_update_subid = InvalidSubTransactionId;
-		write_group_file(grel);
-		heap_close(grel, NoLock);
-	}
-
 	/*
 	 * Signal the postmaster to reload its password & group-file cache.
 	 */
@@ -513,17 +396,11 @@
 	{
 		if (user_file_update_subid == mySubid)
 			user_file_update_subid = parentSubid;
-
-		if (group_file_update_subid == mySubid)
-			group_file_update_subid = parentSubid;
 	}
 	else
 	{
 		if (user_file_update_subid == mySubid)
 			user_file_update_subid = InvalidSubTransactionId;
-
-		if (group_file_update_subid == mySubid)
-			group_file_update_subid = InvalidSubTransactionId;
 	}
 }
 
@@ -550,6 +427,7 @@
 	char		encrypted_password[MD5_PASSWD_LEN + 1];
 	int			sysid = 0;		/* PgSQL system id (valid if havesysid) */
 	bool		createdb = false;		/* Can the user create databases? */
+	bool		isgroup = false;		/* Can the user create databases? */
 	bool		createuser = false;		/* Can this user create users? */
 	List	   *groupElts = NIL;	/* The groups the user is a member of */
 	char	   *validUntil = NULL;		/* The time the login is valid
@@ -677,9 +555,9 @@
 		Form_pg_shadow shadow_form = (Form_pg_shadow) GETSTRUCT(tuple);
 		int32		this_sysid;
 
-		user_exists = (strcmp(NameStr(shadow_form->usename), stmt->user) == 0);
+		user_exists = (strcmp(NameStr(shadow_form->rolename), stmt->user) == 0);
 
-		this_sysid = shadow_form->usesysid;
+		this_sysid = shadow_form->rolesysid;
 		if (havesysid)			/* customized id wanted */
 			sysid_exists = (this_sysid == sysid);
 		else
@@ -694,12 +572,12 @@
 	if (user_exists)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
-				 errmsg("user \"%s\" already exists",
+				 errmsg("role \"%s\" already exists",
 						stmt->user)));
 	if (sysid_exists)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
-				 errmsg("user ID %d is already assigned", sysid)));
+				 errmsg("role ID %d is already assigned", sysid)));
 
 	/* If no sysid given, use max existing id + 1 */
 	if (!havesysid)
@@ -711,15 +589,17 @@
 	MemSet(new_record, 0, sizeof(new_record));
 	MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
 
-	new_record[Anum_pg_shadow_usename - 1] =
+	new_record[Anum_pg_shadow_rolename - 1] =
 		DirectFunctionCall1(namein, CStringGetDatum(stmt->user));
-	new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(sysid);
+	new_record[Anum_pg_shadow_rolesysid - 1] = Int32GetDatum(sysid);
 	AssertState(BoolIsValid(createdb));
-	new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb);
+	new_record[Anum_pg_shadow_rolecreatedb - 1] = BoolGetDatum(createdb);
 	AssertState(BoolIsValid(createuser));
-	new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser);
+	new_record[Anum_pg_shadow_rolesuper - 1] = BoolGetDatum(createuser);
 	/* superuser gets catupd right by default */
-	new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser);
+	new_record[Anum_pg_shadow_rolecatupd - 1] = BoolGetDatum(createuser);
+	AssertState(BoolIsValid(isgroup));
+	new_record[Anum_pg_shadow_isgroup - 1] = BoolGetDatum(isgroup);
 
 	if (password)
 	{
@@ -745,6 +625,7 @@
 		new_record_nulls[Anum_pg_shadow_valuntil - 1] = 'n';
 
 	new_record_nulls[Anum_pg_shadow_useconfig - 1] = 'n';
+	new_record_nulls[Anum_pg_shadow_grolist - 1] = 'n';
 
 	tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
 
@@ -898,6 +779,11 @@
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("user \"%s\" does not exist", stmt->user)));
 
+	if (((Form_pg_shadow) GETSTRUCT(tuple))->isgroup)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("\"%s\" is not a user", stmt->user)));
+
 	/*
 	 * Build an updated tuple, perusing the information just obtained
 	 */
@@ -905,15 +791,15 @@
 	MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
 	MemSet(new_record_repl, ' ', sizeof(new_record_repl));
 
-	new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
+	new_record[Anum_pg_shadow_rolename - 1] = DirectFunctionCall1(namein,
 											CStringGetDatum(stmt->user));
-	new_record_repl[Anum_pg_shadow_usename - 1] = 'r';
+	new_record_repl[Anum_pg_shadow_rolename - 1] = 'r';
 
 	/* createdb */
 	if (createdb >= 0)
 	{
-		new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb > 0);
-		new_record_repl[Anum_pg_shadow_usecreatedb - 1] = 'r';
+		new_record[Anum_pg_shadow_rolecreatedb - 1] = BoolGetDatum(createdb > 0);
+		new_record_repl[Anum_pg_shadow_rolecreatedb - 1] = 'r';
 	}
 
 	/*
@@ -926,11 +812,11 @@
 	 */
 	if (createuser >= 0)
 	{
-		new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser > 0);
-		new_record_repl[Anum_pg_shadow_usesuper - 1] = 'r';
+		new_record[Anum_pg_shadow_rolesuper - 1] = BoolGetDatum(createuser > 0);
+		new_record_repl[Anum_pg_shadow_rolesuper - 1] = 'r';
 
-		new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser > 0);
-		new_record_repl[Anum_pg_shadow_usecatupd - 1] = 'r';
+		new_record[Anum_pg_shadow_rolecatupd - 1] = BoolGetDatum(createuser > 0);
+		new_record_repl[Anum_pg_shadow_rolecatupd - 1] = 'r';
 	}
 
 	/* password */
@@ -1011,8 +897,13 @@
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("user \"%s\" does not exist", stmt->user)));
 
+	if (((Form_pg_shadow) GETSTRUCT(oldtuple))->isgroup)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("\"%s\" is not a user", stmt->user)));
+
 	if (!(superuser() ||
-		((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetUserId()))
+		((Form_pg_shadow) GETSTRUCT(oldtuple))->rolesysid == GetUserId()))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied")));
@@ -1103,7 +994,12 @@
 					(errcode(ERRCODE_UNDEFINED_OBJECT),
 					 errmsg("user \"%s\" does not exist", user)));
 
-		usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->usesysid;
+		if (((Form_pg_shadow) GETSTRUCT(tuple))->isgroup)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("\"%s\" is not a user", user)));
+
+		usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->rolesysid;
 
 		if (usesysid == GetUserId())
 			ereport(ERROR,
@@ -1172,7 +1068,7 @@
 			AlterGroupStmt ags;
 
 			/* the group name from which to try to drop the user: */
-			ags.name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tmp_tuple))->groname));
+			ags.name = pstrdup(NameStr(((Form_pg_shadow) GETSTRUCT(tmp_tuple))->rolename));
 			ags.action = -1;
 			ags.listUsers = list_make1(makeInteger(usesysid));
 			AlterGroup(&ags, "DROP USER");
@@ -1232,13 +1128,18 @@
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("user \"%s\" does not exist", oldname)));
 
+	if (((Form_pg_shadow) GETSTRUCT(oldtuple))->isgroup)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("\"%s\" is not a user", oldname)));
+
 	/*
 	 * XXX Client applications probably store the session user somewhere,
 	 * so renaming it could cause confusion.  On the other hand, there may
 	 * not be an actual problem besides a little confusion, so think about
 	 * this and decide.
 	 */
-	if (((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetSessionUserId())
+	if (((Form_pg_shadow) GETSTRUCT(oldtuple))->rolesysid == GetSessionUserId())
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("session user may not be renamed")));
@@ -1260,10 +1161,10 @@
 	for (i = 0; i < Natts_pg_shadow; i++)
 		repl_repl[i] = ' ';
 
-	repl_repl[Anum_pg_shadow_usename - 1] = 'r';
-	repl_val[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
+	repl_repl[Anum_pg_shadow_rolename - 1] = 'r';
+	repl_val[Anum_pg_shadow_rolename - 1] = DirectFunctionCall1(namein,
 											   CStringGetDatum(newname));
-	repl_null[Anum_pg_shadow_usename - 1] = ' ';
+	repl_null[Anum_pg_shadow_rolename - 1] = ' ';
 
 	datum = heap_getattr(oldtuple, Anum_pg_shadow_passwd, dsc, &isnull);
 
@@ -1325,16 +1226,17 @@
 void
 CreateGroup(CreateGroupStmt *stmt)
 {
-	Relation	pg_group_rel;
+	Relation	pg_shadow_rel;
 	HeapScanDesc scan;
 	HeapTuple	tuple;
-	TupleDesc	pg_group_dsc;
+	TupleDesc	pg_shadow_dsc;
 	bool		group_exists = false,
 				sysid_exists = false,
-				havesysid = false;
+				havesysid = false,
+				isgroup = true;
 	int			max_id;
-	Datum		new_record[Natts_pg_group];
-	char		new_record_nulls[Natts_pg_group];
+	Datum		new_record[Natts_pg_shadow];
+	char		new_record_nulls[Natts_pg_shadow];
 	ListCell   *item;
 	ListCell   *option;
 	List	   *newlist = NIL;
@@ -1402,20 +1304,20 @@
 	 * to be sure of what the next grosysid should be, and we need to
 	 * protect our eventual update of the flat group file.
 	 */
-	pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
-	pg_group_dsc = RelationGetDescr(pg_group_rel);
+	pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
+	pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
 
-	scan = heap_beginscan(pg_group_rel, SnapshotNow, 0, NULL);
+	scan = heap_beginscan(pg_shadow_rel, SnapshotNow, 0, NULL);
 	max_id = 99;				/* start auto-assigned ids at 100 */
 	while (!group_exists && !sysid_exists &&
 		   (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
-		Form_pg_group group_form = (Form_pg_group) GETSTRUCT(tuple);
+		Form_pg_shadow shadow_form = (Form_pg_shadow) GETSTRUCT(tuple);
 		int32		this_sysid;
 
-		group_exists = (strcmp(NameStr(group_form->groname), stmt->name) == 0);
+		group_exists = (strcmp(NameStr(shadow_form->rolename), stmt->name) == 0);
 
-		this_sysid = group_form->grosysid;
+		this_sysid = shadow_form->rolesysid;
 		if (havesysid)			/* customized id wanted */
 			sysid_exists = (this_sysid == sysid);
 		else
@@ -1430,12 +1332,12 @@
 	if (group_exists)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
-				 errmsg("group \"%s\" already exists",
+				 errmsg("role \"%s\" already exists",
 						stmt->name)));
 	if (sysid_exists)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
-				 errmsg("group ID %d is already assigned", sysid)));
+				 errmsg("role ID %d is already assigned", sysid)));
 
 	/* If no sysid given, use max existing id + 1 */
 	if (!havesysid)
@@ -1462,35 +1364,41 @@
 	/*
 	 * Form a tuple to insert
 	 */
-	new_record[Anum_pg_group_groname - 1] =
+	new_record[Anum_pg_shadow_rolename - 1] =
 		DirectFunctionCall1(namein, CStringGetDatum(stmt->name));
-	new_record[Anum_pg_group_grosysid - 1] = Int32GetDatum(sysid);
-	new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(grolist);
+	new_record[Anum_pg_shadow_rolesysid - 1] = Int32GetDatum(sysid);
 
-	new_record_nulls[Anum_pg_group_groname - 1] = ' ';
-	new_record_nulls[Anum_pg_group_grosysid - 1] = ' ';
-	new_record_nulls[Anum_pg_group_grolist - 1] = grolist ? ' ' : 'n';
+	AssertState(BoolIsValid(isgroup));
+	new_record[Anum_pg_shadow_isgroup - 1] = BoolGetDatum(isgroup);
+	new_record[Anum_pg_shadow_grolist - 1] = PointerGetDatum(grolist);
+
+	new_record_nulls[Anum_pg_shadow_rolename - 1] = ' ';
+	new_record_nulls[Anum_pg_shadow_rolesysid - 1] = ' ';
+	new_record_nulls[Anum_pg_shadow_rolecreatedb - 1] = 'n';
+	new_record_nulls[Anum_pg_shadow_rolesuper - 1] = 'n';
+	new_record_nulls[Anum_pg_shadow_rolecatupd - 1] = 'n';
+	new_record_nulls[Anum_pg_shadow_grolist - 1] = grolist ? ' ' : 'n';
 
-	tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
+	tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
 
 	/*
 	 * Insert a new record in the pg_group table
 	 */
-	simple_heap_insert(pg_group_rel, tuple);
+	simple_heap_insert(pg_shadow_rel, tuple);
 
 	/* Update indexes */
-	CatalogUpdateIndexes(pg_group_rel, tuple);
+	CatalogUpdateIndexes(pg_shadow_rel, tuple);
 
 	/*
 	 * Now we can clean up; but keep lock until commit (to avoid possible
 	 * deadlock when commit code tries to acquire lock).
 	 */
-	heap_close(pg_group_rel, NoLock);
+	heap_close(pg_shadow_rel, NoLock);
 
 	/*
 	 * Set flag to update flat group file at commit.
 	 */
-	group_file_update_needed();
+	user_file_update_needed();
 }
 
 
@@ -1500,8 +1408,8 @@
 void
 AlterGroup(AlterGroupStmt *stmt, const char *tag)
 {
-	Relation	pg_group_rel;
-	TupleDesc	pg_group_dsc;
+	Relation	pg_shadow_rel;
+	TupleDesc	pg_shadow_dsc;
 	HeapTuple	group_tuple;
 	IdList	   *oldarray;
 	Datum		datum;
@@ -1520,13 +1428,13 @@
 	/*
 	 * Secure exclusive lock to protect our update of the flat group file.
 	 */
-	pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
-	pg_group_dsc = RelationGetDescr(pg_group_rel);
+	pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
+	pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
 
 	/*
 	 * Fetch existing tuple for group.
 	 */
-	group_tuple = SearchSysCache(GRONAME,
+	group_tuple = SearchSysCache(SHADOWNAME,
 								 PointerGetDatum(stmt->name),
 								 0, 0, 0);
 	if (!HeapTupleIsValid(group_tuple))
@@ -1534,9 +1442,14 @@
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("group \"%s\" does not exist", stmt->name)));
 
+	if (!(((Form_pg_shadow) GETSTRUCT(group_tuple))->isgroup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("\"%s\" is not a group", stmt->name)));
+
 	/* Fetch old group membership. */
-	datum = heap_getattr(group_tuple, Anum_pg_group_grolist,
-						 pg_group_dsc, &null);
+	datum = heap_getattr(group_tuple, Anum_pg_shadow_grolist,
+						 pg_shadow_dsc, &null);
 	oldarray = null ? NULL : DatumGetIdListP(datum);
 
 	/* initialize list with old array contents */
@@ -1582,7 +1495,7 @@
 		}
 
 		/* Do the update */
-		UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
+		UpdateGroupMembership(pg_shadow_rel, group_tuple, newlist);
 	}							/* endif alter group add user */
 
 	else if (stmt->action == -1)	/* drop users from group */
@@ -1627,7 +1540,7 @@
 			}
 
 			/* Do the update */
-			UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
+			UpdateGroupMembership(pg_shadow_rel, group_tuple, newlist);
 		}						/* endif group not null */
 	}							/* endif alter group drop user */
 
@@ -1637,12 +1550,12 @@
 	 * Now we can clean up; but keep lock until commit (to avoid possible
 	 * deadlock when commit code tries to acquire lock).
 	 */
-	heap_close(pg_group_rel, NoLock);
+	heap_close(pg_shadow_rel, NoLock);
 
 	/*
 	 * Set flag to update flat group file at commit.
 	 */
-	group_file_update_needed();
+	user_file_update_needed();
 }
 
 /*
@@ -1655,9 +1568,9 @@
 					  List *members)
 {
 	IdList	   *newarray;
-	Datum		new_record[Natts_pg_group];
-	char		new_record_nulls[Natts_pg_group];
-	char		new_record_repl[Natts_pg_group];
+	Datum		new_record[Natts_pg_shadow];
+	char		new_record_nulls[Natts_pg_shadow];
+	char		new_record_repl[Natts_pg_shadow];
 	HeapTuple	tuple;
 
 	newarray = IdListToArray(members);
@@ -1669,8 +1582,8 @@
 	MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
 	MemSet(new_record_repl, ' ', sizeof(new_record_repl));
 
-	new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);
-	new_record_repl[Anum_pg_group_grolist - 1] = 'r';
+	new_record[Anum_pg_shadow_grolist - 1] = PointerGetDatum(newarray);
+	new_record_repl[Anum_pg_shadow_grolist - 1] = 'r';
 
 	tuple = heap_modifytuple(group_tuple, group_rel,
 						  new_record, new_record_nulls, new_record_repl);
@@ -1745,7 +1658,7 @@
 void
 DropGroup(DropGroupStmt *stmt)
 {
-	Relation	pg_group_rel;
+	Relation	pg_shadow_rel;
 	HeapTuple	tuple;
 
 	/*
@@ -1759,7 +1672,7 @@
 	/*
 	 * Secure exclusive lock to protect our update of the flat group file.
 	 */
-	pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
+	pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
 
 	/* Find and delete the group. */
 
@@ -1771,18 +1684,23 @@
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("group \"%s\" does not exist", stmt->name)));
 
-	simple_heap_delete(pg_group_rel, &tuple->t_self);
+	if (!(((Form_pg_shadow) GETSTRUCT(tuple))->isgroup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("\"%s\" is not a group", stmt->name)));
+
+	simple_heap_delete(pg_shadow_rel, &tuple->t_self);
 
 	/*
 	 * Now we can clean up; but keep lock until commit (to avoid possible
 	 * deadlock when commit code tries to acquire lock).
 	 */
-	heap_close(pg_group_rel, NoLock);
+	heap_close(pg_shadow_rel, NoLock);
 
 	/*
 	 * Set flag to update flat group file at commit.
 	 */
-	group_file_update_needed();
+	user_file_update_needed();
 }
 
 
@@ -1796,9 +1714,9 @@
 	Relation	rel;
 
 	/* ExclusiveLock because we need to update the flat group file */
-	rel = heap_openr(GroupRelationName, ExclusiveLock);
+	rel = heap_openr(ShadowRelationName, ExclusiveLock);
 
-	tup = SearchSysCacheCopy(GRONAME,
+	tup = SearchSysCacheCopy(SHADOWNAME,
 							 CStringGetDatum(oldname),
 							 0, 0, 0);
 	if (!HeapTupleIsValid(tup))
@@ -1806,13 +1724,18 @@
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("group \"%s\" does not exist", oldname)));
 
+	if (!(((Form_pg_shadow) GETSTRUCT(tup))->isgroup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("\"%s\" is not a group", oldname)));
+
 	/* make sure the new name doesn't exist */
-	if (SearchSysCacheExists(GRONAME,
+	if (SearchSysCacheExists(SHADOWNAME,
 							 CStringGetDatum(newname),
 							 0, 0, 0))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
-				 errmsg("group \"%s\" already exists", newname)));
+				 errmsg("role \"%s\" already exists", newname)));
 
 	/* must be superuser */
 	if (!superuser())
@@ -1821,12 +1744,12 @@
 				 errmsg("must be superuser to rename groups")));
 
 	/* rename */
-	namestrcpy(&(((Form_pg_group) GETSTRUCT(tup))->groname), newname);
+	namestrcpy(&(((Form_pg_shadow) GETSTRUCT(tup))->rolename), newname);
 	simple_heap_update(rel, &tup->t_self, tup);
 	CatalogUpdateIndexes(rel, tup);
 
 	heap_close(rel, NoLock);
 	heap_freetuple(tup);
 
-	group_file_update_needed();
+	user_file_update_needed();
 }
Index: src/backend/commands/variable.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/commands/variable.c,v
retrieving revision 1.105
diff -u -r1.105 variable.c
--- src/backend/commands/variable.c	31 Dec 2004 21:59:42 -0000	1.105
+++ src/backend/commands/variable.c	21 Jan 2005 05:43:47 -0000
@@ -665,8 +665,8 @@
 			return NULL;
 		}
 
-		usesysid = ((Form_pg_shadow) GETSTRUCT(userTup))->usesysid;
-		is_superuser = ((Form_pg_shadow) GETSTRUCT(userTup))->usesuper;
+		usesysid = ((Form_pg_shadow) GETSTRUCT(userTup))->rolesysid;
+		is_superuser = ((Form_pg_shadow) GETSTRUCT(userTup))->rolesuper;
 		actual_username = value;
 
 		ReleaseSysCache(userTup);
Index: src/backend/libpq/hba.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/libpq/hba.c,v
retrieving revision 1.136
diff -u -r1.136 hba.c
--- src/backend/libpq/hba.c	31 Dec 2004 21:59:50 -0000	1.136
+++ src/backend/libpq/hba.c	21 Jan 2005 05:43:47 -0000
@@ -67,10 +67,6 @@
 static List *ident_lines = NIL;
 static List *ident_line_nums = NIL;
 
-/* pre-parsed content of group file and corresponding line #s */
-static List *group_lines = NIL;
-static List *group_line_nums = NIL;
-
 /* pre-parsed content of user passwd file and corresponding line #s */
 static List *user_lines = NIL;
 static List *user_line_nums = NIL;
@@ -919,64 +915,7 @@
 
 
 /*
- *	 Load group/user name mapping file
- */
-void
-load_group(void)
-{
-	char	   *filename;
-	FILE	   *group_file;
-
-	/* Discard any old data */
-	if (group_lines || group_line_nums)
-		free_lines(&group_lines, &group_line_nums);
-	if (group_sorted)
-		pfree(group_sorted);
-	group_sorted = NULL;
-	group_length = 0;
-
-	/* Read in the file contents */
-	filename = group_getfilename();
-	group_file = AllocateFile(filename, "r");
-
-	if (group_file == NULL)
-	{
-		/* no complaint if not there */
-		if (errno != ENOENT)
-			ereport(LOG,
-					(errcode_for_file_access(),
-					 errmsg("could not open file \"%s\": %m", filename)));
-		pfree(filename);
-		return;
-	}
-
-	tokenize_file(filename, group_file, &group_lines, &group_line_nums);
-
-	FreeFile(group_file);
-	pfree(filename);
-
-	/* create sorted lines for binary searching */
-	group_length = list_length(group_lines);
-	if (group_length)
-	{
-		int			i = 0;
-		ListCell   *line;
-
-		group_sorted = palloc(group_length * sizeof(List *));
-
-		foreach(line, group_lines)
-			group_sorted[i++] = lfirst(line);
-
-		qsort((void *) group_sorted,
-			  group_length,
-			  sizeof(List *),
-			  user_group_qsort_cmp);
-	}
-}
-
-
-/*
- *	 Load user/password mapping file
+ *	 Load user/password/group mapping file
  */
 void
 load_user(void)
@@ -1014,20 +953,31 @@
 
 	/* create sorted lines for binary searching */
 	user_length = list_length(user_lines);
+	group_length = list_length(user_lines);
 	if (user_length)
 	{
-		int			i = 0;
+		int			i = 0, j = 0;
 		ListCell   *line;
 
 		user_sorted = palloc(user_length * sizeof(List *));
+		group_sorted = palloc(group_length * sizeof(List *));
+
+		foreach(line, user_lines) {
+      if (!memcmp(lfirst(line),"+",1)) group_sorted[j++] = lfirst(line); else user_sorted[i++] = lfirst(line);
+    }
 
-		foreach(line, user_lines)
-			user_sorted[i++] = lfirst(line);
+    user_length = i;
+    group_length = j;
 
 		qsort((void *) user_sorted,
 			  user_length,
 			  sizeof(List *),
 			  user_group_qsort_cmp);
+
+		qsort((void *) group_sorted,
+			  group_length,
+			  sizeof(List *),
+			  user_group_qsort_cmp);
 	}
 }
 
Index: src/backend/postmaster/postmaster.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/postmaster/postmaster.c,v
retrieving revision 1.443
diff -u -r1.443 postmaster.c
--- src/backend/postmaster/postmaster.c	12 Jan 2005 16:38:17 -0000	1.443
+++ src/backend/postmaster/postmaster.c	21 Jan 2005 05:43:48 -0000
@@ -908,7 +908,6 @@
 	load_hba();
 	load_ident();
 	load_user();
-	load_group();
 
 	/*
 	 * We're ready to rock and roll...
@@ -2681,7 +2680,6 @@
 	load_hba();
 	load_ident();
 	load_user();
-	load_group();
 #endif
 
 	/*
@@ -3309,7 +3307,6 @@
 		 * Password or group file has changed.
 		 */
 		load_user();
-		load_group();
 	}
 
 	if (CheckPostmasterSignal(PMSIGNAL_WAKEN_CHILDREN))
Index: src/backend/utils/adt/acl.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/adt/acl.c,v
retrieving revision 1.113
diff -u -r1.113 acl.c
--- src/backend/utils/adt/acl.c	31 Dec 2004 22:01:21 -0000	1.113
+++ src/backend/utils/adt/acl.c	21 Jan 2005 05:43:48 -0000
@@ -393,7 +393,7 @@
 								  0, 0, 0);
 			if (HeapTupleIsValid(htup))
 			{
-				putid(p, NameStr(((Form_pg_shadow) GETSTRUCT(htup))->usename));
+				putid(p, NameStr(((Form_pg_shadow) GETSTRUCT(htup))->rolename));
 				ReleaseSysCache(htup);
 			}
 			else
@@ -442,7 +442,7 @@
 						  0, 0, 0);
 	if (HeapTupleIsValid(htup))
 	{
-		putid(p, NameStr(((Form_pg_shadow) GETSTRUCT(htup))->usename));
+		putid(p, NameStr(((Form_pg_shadow) GETSTRUCT(htup))->rolename));
 		ReleaseSysCache(htup);
 	}
 	else
@@ -1088,40 +1088,48 @@
 	int			i,
 				num;
 
-	tuple = SearchSysCache(GROSYSID,
+	tuple = SearchSysCache(SHADOWSYSID,
 						   ObjectIdGetDatum(gid),
 						   0, 0, 0);
-	if (HeapTupleIsValid(tuple))
-	{
-		att = SysCacheGetAttr(GROSYSID,
-							  tuple,
-							  Anum_pg_group_grolist,
-							  &isNull);
-		if (!isNull)
+  if (!HeapTupleIsValid(tuple)) {
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("group with ID %u does not exist", gid)));
+    return result;
+  }
+
+  if (!(((Form_pg_shadow) GETSTRUCT(tuple))->isgroup)) {
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("ID %u is not a group", gid)));
+    return result;
+  }
+
+	att = SysCacheGetAttr(SHADOWSYSID,
+						  tuple,
+						  Anum_pg_shadow_grolist,
+						  &isNull);
+	if (!isNull)
+	{
+		/* be sure the IdList is not toasted */
+		glist = DatumGetIdListP(att);
+		/* scan it */
+		num = IDLIST_NUM(glist);
+		aidp = IDLIST_DAT(glist);
+		for (i = 0; i < num; ++i)
 		{
-			/* be sure the IdList is not toasted */
-			glist = DatumGetIdListP(att);
-			/* scan it */
-			num = IDLIST_NUM(glist);
-			aidp = IDLIST_DAT(glist);
-			for (i = 0; i < num; ++i)
+			if (aidp[i] == uid)
 			{
-				if (aidp[i] == uid)
-				{
-					result = true;
-					break;
-				}
+				result = true;
+				break;
 			}
-			/* if IdList was toasted, free detoasted copy */
-			if ((Pointer) glist != DatumGetPointer(att))
-				pfree(glist);
 		}
-		ReleaseSysCache(tuple);
+		/* if IdList was toasted, free detoasted copy */
+		if ((Pointer) glist != DatumGetPointer(att))
+			pfree(glist);
 	}
-	else
-		ereport(WARNING,
-				(errcode(ERRCODE_UNDEFINED_OBJECT),
-				 errmsg("group with ID %u does not exist", gid)));
+	ReleaseSysCache(tuple);
+
 	return result;
 }
 
Index: src/backend/utils/adt/ruleutils.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v
retrieving revision 1.188
diff -u -r1.188 ruleutils.c
--- src/backend/utils/adt/ruleutils.c	13 Jan 2005 17:19:10 -0000	1.188
+++ src/backend/utils/adt/ruleutils.c	21 Jan 2005 05:43:48 -0000
@@ -1211,7 +1211,7 @@
 	if (HeapTupleIsValid(usertup))
 	{
 		user_rec = (Form_pg_shadow) GETSTRUCT(usertup);
-		StrNCpy(NameStr(*result), NameStr(user_rec->usename), NAMEDATALEN);
+		StrNCpy(NameStr(*result), NameStr(user_rec->rolename), NAMEDATALEN);
 		ReleaseSysCache(usertup);
 	}
 	else
Index: src/backend/utils/cache/lsyscache.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v
retrieving revision 1.119
diff -u -r1.119 lsyscache.c
--- src/backend/utils/cache/lsyscache.c	31 Dec 2004 22:01:25 -0000	1.119
+++ src/backend/utils/cache/lsyscache.c	21 Jan 2005 05:43:49 -0000
@@ -2023,16 +2023,16 @@
  *
  *	  Given a user name, look up the user's sysid.
  *	  Raises an error if no such user (rather than returning zero,
- *	  which might possibly be a valid usesysid).
+ *	  which might possibly be a valid rolesysid).
  *
- * Note: the type of usesysid is currently int4, but may change to Oid
+ * Note: the type of rolesysid is currently int4, but may change to Oid
  * someday.  It'd be reasonable to return zero on failure if we were
  * using Oid ...
  */
 AclId
 get_usesysid(const char *username)
 {
-	int32		result;
+	AclId		result;
 	HeapTuple	userTup;
 
 	userTup = SearchSysCache(SHADOWNAME,
@@ -2043,9 +2043,75 @@
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("user \"%s\" does not exist", username)));
 
-	result = ((Form_pg_shadow) GETSTRUCT(userTup))->usesysid;
+	if ((((Form_pg_shadow) GETSTRUCT(userTup))->isgroup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("\"%s\" is not a user", username)));
+
+	result = ((Form_pg_shadow) GETSTRUCT(userTup))->rolesysid;
 
 	ReleaseSysCache(userTup);
 
 	return result;
 }
+
+/*
+ * get_grosysid
+ *
+ *	  Given a group name, look up the group's sysid.
+ *	  Raises an error if no such group (rather than returning zero,
+ *	  which might possibly be a valid grosysid).
+ *
+ * Note: the type of rolesysid is currently int4, but may change to Oid
+ * someday.  It'd be reasonable to return zero on failure if we were
+ * using Oid ...
+ */
+AclId
+get_grosysid(char *groname)
+{
+	AclId		id = 0;
+	HeapTuple	groupTup;
+
+	groupTup = SearchSysCache(SHADOWNAME,
+						   PointerGetDatum(groname),
+						   0, 0, 0);
+	if (!HeapTupleIsValid(groupTup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("group \"%s\" does not exist", groname)));
+
+  if (!(((Form_pg_shadow) GETSTRUCT(groupTup))->isgroup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("\"%s\" is not a group", groname)));
+
+	id = ((Form_pg_shadow) GETSTRUCT(groupTup))->rolesysid;
+	ReleaseSysCache(groupTup);
+
+	return id;
+}
+
+/*
+ * get_groname
+ *
+ * Convert group ID to name, or return NULL if group can't be found
+ */
+char *
+get_groname(AclId grosysid)
+{
+	char	   *name = NULL;
+	HeapTuple	groupTup;
+
+	groupTup = SearchSysCache(SHADOWSYSID,
+						   ObjectIdGetDatum(grosysid),
+						   0, 0, 0);
+	if (HeapTupleIsValid(groupTup))
+	{
+    if ((((Form_pg_shadow) GETSTRUCT(groupTup))->isgroup))
+		  name = pstrdup(NameStr(((Form_pg_shadow) GETSTRUCT(groupTup))->rolename));
+
+	  ReleaseSysCache(groupTup);
+	}
+	return name;
+}
+
Index: src/backend/utils/cache/syscache.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/cache/syscache.c,v
retrieving revision 1.96
diff -u -r1.96 syscache.c
--- src/backend/utils/cache/syscache.c	31 Dec 2004 22:01:25 -0000	1.96
+++ src/backend/utils/cache/syscache.c	21 Jan 2005 05:43:49 -0000
@@ -389,7 +389,7 @@
 		0,
 		1,
 		{
-			Anum_pg_shadow_usename,
+			Anum_pg_shadow_rolename,
 			0,
 			0,
 			0
@@ -399,7 +399,7 @@
 		0,
 		1,
 		{
-			Anum_pg_shadow_usesysid,
+			Anum_pg_shadow_rolesysid,
 			0,
 			0,
 			0
Index: src/backend/utils/init/miscinit.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/init/miscinit.c,v
retrieving revision 1.137
diff -u -r1.137 miscinit.c
--- src/backend/utils/init/miscinit.c	31 Dec 2004 22:01:40 -0000	1.137
+++ src/backend/utils/init/miscinit.c	21 Jan 2005 05:43:49 -0000
@@ -315,7 +315,7 @@
 	HeapTuple	userTup;
 	Datum		datum;
 	bool		isnull;
-	AclId		usesysid;
+	AclId		rolesysid;
 
 	/*
 	 * Don't do scans if we're bootstrapping, none of the system catalogs
@@ -334,12 +334,12 @@
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("user \"%s\" does not exist", username)));
 
-	usesysid = ((Form_pg_shadow) GETSTRUCT(userTup))->usesysid;
+	rolesysid = ((Form_pg_shadow) GETSTRUCT(userTup))->rolesysid;
 
-	AuthenticatedUserId = usesysid;
-	AuthenticatedUserIsSuperuser = ((Form_pg_shadow) GETSTRUCT(userTup))->usesuper;
+	AuthenticatedUserId = rolesysid;
+	AuthenticatedUserIsSuperuser = ((Form_pg_shadow) GETSTRUCT(userTup))->rolesuper;
 
-	SetSessionUserId(usesysid); /* sets CurrentUserId too */
+	SetSessionUserId(rolesysid); /* sets CurrentUserId too */
 
 	/* Record username and superuser status as GUC settings too */
 	SetConfigOption("session_authorization", username,
@@ -428,7 +428,7 @@
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("invalid user ID: %d", userid)));
 
-	result = pstrdup(NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename));
+	result = pstrdup(NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->rolename));
 
 	ReleaseSysCache(tuple);
 	return result;
Index: src/backend/utils/misc/superuser.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/misc/superuser.c,v
retrieving revision 1.30
diff -u -r1.30 superuser.c
--- src/backend/utils/misc/superuser.c	31 Dec 2004 22:02:45 -0000	1.30
+++ src/backend/utils/misc/superuser.c	21 Jan 2005 05:43:49 -0000
@@ -53,7 +53,7 @@
 						  0, 0, 0);
 	if (HeapTupleIsValid(utup))
 	{
-		result = ((Form_pg_shadow) GETSTRUCT(utup))->usesuper;
+		result = ((Form_pg_shadow) GETSTRUCT(utup))->rolesuper;
 		ReleaseSysCache(utup);
 	}
 	return result;
Index: src/include/catalog/pg_shadow.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/catalog/pg_shadow.h,v
retrieving revision 1.27
diff -u -r1.27 pg_shadow.h
--- src/include/catalog/pg_shadow.h	31 Dec 2004 22:03:26 -0000	1.27
+++ src/include/catalog/pg_shadow.h	21 Jan 2005 05:43:49 -0000
@@ -31,16 +31,18 @@
  */
 CATALOG(pg_shadow) BOOTSTRAP BKI_SHARED_RELATION BKI_WITHOUT_OIDS
 {
-	NameData	usename;
-	int4		usesysid;
-	bool		usecreatedb;
-	bool		usesuper;		/* read this field via superuser() only */
-	bool		usecatupd;
+	NameData	rolename;
+	int4		rolesysid;
+	bool		rolecreatedb;
+	bool		rolesuper;		/* read this field via superuser() only */
+	bool		rolecatupd;
+  bool    isgroup;
 
 	/* remaining fields may be null; use heap_getattr to read them! */
 	text		passwd;
 	int4		valuntil;		/* actually abstime */
 	text		useconfig[1];
+  int4    grolist[1];
 } FormData_pg_shadow;
 
 /* ----------------
@@ -54,15 +56,17 @@
  *		compiler constants for pg_shadow
  * ----------------
  */
-#define Natts_pg_shadow				8
-#define Anum_pg_shadow_usename			1
-#define Anum_pg_shadow_usesysid			2
-#define Anum_pg_shadow_usecreatedb		3
-#define Anum_pg_shadow_usesuper			4
-#define Anum_pg_shadow_usecatupd		5
-#define Anum_pg_shadow_passwd			6
-#define Anum_pg_shadow_valuntil			7
-#define Anum_pg_shadow_useconfig		8
+#define Natts_pg_shadow				10
+#define Anum_pg_shadow_rolename			1
+#define Anum_pg_shadow_rolesysid			2
+#define Anum_pg_shadow_rolecreatedb		3
+#define Anum_pg_shadow_rolesuper			4
+#define Anum_pg_shadow_rolecatupd		5
+#define Anum_pg_shadow_isgroup		6
+#define Anum_pg_shadow_passwd			7
+#define Anum_pg_shadow_valuntil			8
+#define Anum_pg_shadow_useconfig		9
+#define Anum_pg_shadow_grolist		10
 
 /* ----------------
  *		initial contents of pg_shadow
@@ -71,7 +75,7 @@
  * user choices.
  * ----------------
  */
-DATA(insert ( "POSTGRES" PGUID t t t _null_ _null_ _null_ ));
+DATA(insert ( "POSTGRES" PGUID t t t f _null_ _null_ _null_ _null_ ));
 
 #define BOOTSTRAP_USESYSID 1
 
Index: src/include/libpq/hba.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/libpq/hba.h,v
retrieving revision 1.35
diff -u -r1.35 hba.h
--- src/include/libpq/hba.h	2 Feb 2004 16:58:30 -0000	1.35
+++ src/include/libpq/hba.h	21 Jan 2005 05:43:49 -0000
@@ -38,7 +38,6 @@
 extern void load_hba(void);
 extern void load_ident(void);
 extern void load_user(void);
-extern void load_group(void);
 extern int	hba_getauthmethod(hbaPort *port);
 extern int	authident(hbaPort *port);
 
Index: src/include/utils/acl.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/utils/acl.h,v
retrieving revision 1.76
diff -u -r1.76 acl.h
--- src/include/utils/acl.h	31 Dec 2004 22:03:45 -0000	1.76
+++ src/include/utils/acl.h	21 Jan 2005 05:43:49 -0000
@@ -41,8 +41,8 @@
  * AclIdType	tag that describes if the AclId is a user, group, etc.
  */
 #define ACL_IDTYPE_WORLD		0x00	/* PUBLIC */
-#define ACL_IDTYPE_UID			0x01	/* user id - from pg_shadow */
-#define ACL_IDTYPE_GID			0x02	/* group id - from pg_group */
+#define ACL_IDTYPE_UID			0x01	/* user id - from pg_shadow, isgroup false */
+#define ACL_IDTYPE_GID			0x02	/* group id - from pg_shadow, isgroup true */
 
 /*
  * AclItem
Index: src/include/utils/lsyscache.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/utils/lsyscache.h,v
retrieving revision 1.93
diff -u -r1.93 lsyscache.h
--- src/include/utils/lsyscache.h	31 Dec 2004 22:03:46 -0000	1.93
+++ src/include/utils/lsyscache.h	21 Jan 2005 05:43:49 -0000
@@ -115,7 +115,10 @@
 				  Datum *values, int nvalues,
 				  float4 *numbers, int nnumbers);
 extern char *get_namespace_name(Oid nspid);
-extern int32 get_usesysid(const char *username);
+extern AclId get_usesysid(const char *username);
+extern AclId get_grosysid(char *groname);
+extern char *get_groname(AclId grosysid);
+
 
 #define is_array_type(typid)  (get_element_type(typid) != InvalidOid)
 
#2Euler Taveira de Oliveira
eulerto@yahoo.com.br
In reply to: Stephen Frost (#1)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

Hi Stephen and Hackers,

Moved to -hackers.

Here's a proof-of-concept pretty much untested (it compiles) patch
against HEAD for review of the general approach I'm taking to
merging pg_shadow and pg_group. This is in order to support group
ownership and eventually roles.

I have to disagree with your model. Roles are not so simple like you
try to describe in your patch. I'm suposing this because your using
role* in all of the 'pg_shadow'.
What's Role? A set of relations with their respective privileges and
a set of users and/or roles.

Sometime ago, I drafted a model I think it can be useful. Here it is:

Another catalog relation named 'pg_role' with the following members:
- rolsysid (role id)
- rolname (role name)
- rolowner (role owner)
- rolmembs[] (list of users that belong to the role)
- rolrels[] (list of relations + their permissions)
- hasroles (have dependent roles?)

where:

rolmembs[] is:
- userid (user id or group id or role id)

rolrels[] is:
- relid (relation oid)
- rs_privs (privileges)

What do we do with 'groups'? Well, we can have three categories of
object owners: users, groups and roles. So the 'group owner' can be
implemented with this model.

What about dependent roles? It will be in 'pg_depend'.

Advantages:
1. Don't require changing the actual catalog model. Just an increment.
2. Can't introduce too much overhead. Once roles are in another catalog
table, we need to search it only if it's required.
3. All serious commercial databases have it. And of course, PostgreSQL
community want it too. :-)

Disadvantages:
1. Some overhead when checking for roles and dependent roles.

Comments and/or ideas?

=====
Euler Taveira de Oliveira
euler[at]yahoo_com_br

__________________________________________________
Converse com seus amigos em tempo real com o Yahoo! Messenger
http://br.download.yahoo.com/messenger/

#3Stephen Frost
sfrost@snowman.net
In reply to: Euler Taveira de Oliveira (#2)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

* Euler Taveira de Oliveira (eulerto@yahoo.com.br) wrote:

Here's a proof-of-concept pretty much untested (it compiles) patch
against HEAD for review of the general approach I'm taking to
merging pg_shadow and pg_group. This is in order to support group
ownership and eventually roles.

I have to disagree with your model. Roles are not so simple like you
try to describe in your patch. I'm suposing this because your using
role* in all of the 'pg_shadow'.

The particular name isn't really important- and don't take it to mean
very much...

What's Role? A set of relations with their respective privileges and
a set of users and/or roles.

That's a good question- I'm not really very familiar with roles. :) I'm
honestly more interested in group ownership...

Advantages:
1. Don't require changing the actual catalog model. Just an increment.

I'm not sure what the value of this is..

2. Can't introduce too much overhead. Once roles are in another catalog
table, we need to search it only if it's required.

ok.

3. All serious commercial databases have it. And of course, PostgreSQL
community want it too. :-)

Well, yes, we want roles, we're discussing implementations though, and I
don't see this as an 'advantage' of your approach. :)

Disadvantages:
1. Some overhead when checking for roles and dependent roles.

It was Tom's suggestion that pg_shadow and pg_group be merged to
guarntee unique in the 'id's, which needs to be there unless you want to
change pg_object (iirc? whatever table it is) to handle additional
information about what kind of 'id' it is (role, user or group).

Stephen

#4Euler Taveira de Oliveira
eulerto@yahoo.com.br
In reply to: Stephen Frost (#3)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

Hi Stephen,

I have to disagree with your model. Roles are not so simple like

you

try to describe in your patch. I'm suposing this because your using
role* in all of the 'pg_shadow'.

The particular name isn't really important- and don't take it to mean
very much...

OK. So let it 'use*'.

What's Role? A set of relations with their respective privileges

and

a set of users and/or roles.

That's a good question- I'm not really very familiar with roles. :)
I'm
honestly more interested in group ownership...

OK. Thinking better, keep up your work. I'm going to keep my eyes on
it.

=====
Euler Taveira de Oliveira
euler[at]yahoo_com_br

_______________________________________________________
Yahoo! Acesso Gr�tis - Instale o discador do Yahoo! agora. http://br.acesso.yahoo.com/ - Internet r�pida e gr�tis

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephen Frost (#1)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

Stephen Frost <sfrost@snowman.net> writes:

Here's a proof-of-concept pretty much untested (it compiles) patch
against HEAD for review of the general approach I'm taking to
merging pg_shadow and pg_group. This is in order to support group
ownership and eventually roles. This patch includes my grammar and
get_grosysid move patches, and so conflicts with them.

One point is that you can't simply whack pg_shadow around and eliminate
pg_group, because that will break lord-knows-how-much client software
that looks at these tables. What I'm envisioning is to create a new
system catalog (say pg_role) that holds the New Truth, and then make
pg_shadow and pg_group be predefined views on this catalog that provide
as much backwards compatibility as we can manage.

I believe this was done once before already --- I think that the pg_user
view exists to emulate a prior incarnation of pg_shadow.

A related point is that I hope soon to get rid of type AclId and
usesysid/grosysid/rolesysid and start identifying roles by Oids.
This is connected to Alvaro's work to create proper dependencies
for object owners and privilege entries: once that exists and you
can't drop a referenced role, there will be no need to allow explicit
setting of the SYSID for a new user. Not sure if you want to do any
of the associated changes in your patch, but if int4 is bugging you
then feel free to change it.

regards, tom lane

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Euler Taveira de Oliveira (#2)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

Euler Taveira de Oliveira <eulerto@yahoo.com.br> writes:

What's Role? A set of relations with their respective privileges and
a set of users and/or roles.

Huh? How did relations get into it?

What do we do with 'groups'? Well, we can have three categories of
object owners: users, groups and roles. So the 'group owner' can be
implemented with this model.

Why wouldn't we fold all three into one concept? In particular, I
really fail to see why we'd still keep a separate notion of groups.

regards, tom lane

#7Stephen Frost
sfrost@snowman.net
In reply to: Tom Lane (#5)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

Stephen Frost <sfrost@snowman.net> writes:

Here's a proof-of-concept pretty much untested (it compiles) patch
against HEAD for review of the general approach I'm taking to
merging pg_shadow and pg_group. This is in order to support group
ownership and eventually roles. This patch includes my grammar and
get_grosysid move patches, and so conflicts with them.

One point is that you can't simply whack pg_shadow around and eliminate
pg_group, because that will break lord-knows-how-much client software
that looks at these tables. What I'm envisioning is to create a new
system catalog (say pg_role) that holds the New Truth, and then make
pg_shadow and pg_group be predefined views on this catalog that provide
as much backwards compatibility as we can manage.

Ok. Can I get some help defining what the New Truth will look like
then? I understand users and groups pretty well but I'm not 100% sure
about roles. Is it as simple as what my changed pg_shadow looks like?
What's the difference between a role, a user and a group? Can a role
log in/have a password? Can a role own an object? If a role owns an
object, can any users who have that role {drop, create index, etc} it?

Once we get the layout of pg_role defined I think I'll be able to make
much better progress towards what you're looking for. :)

A related point is that I hope soon to get rid of type AclId and
usesysid/grosysid/rolesysid and start identifying roles by Oids.

Alright. That doesn't sound too bad.

This is connected to Alvaro's work to create proper dependencies
for object owners and privilege entries: once that exists and you
can't drop a referenced role, there will be no need to allow explicit
setting of the SYSID for a new user. Not sure if you want to do any
of the associated changes in your patch, but if int4 is bugging you
then feel free to change it.

Ok, I probably will. Should I be concerned with trying to make
'smallish' patches that build upon each other (ie: change to pg_role
first, then change AclId to Oid, or whatever) or will one larger patch
that takes care of it all be ok?

Thanks,

Stephen

#8Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephen Frost (#7)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

Stephen Frost <sfrost@snowman.net> writes:

Ok. Can I get some help defining what the New Truth will look like
then? I understand users and groups pretty well but I'm not 100% sure
about roles.

I looked through SQL99 a bit (see 4.31 "Basic security model") and think
I now have some handle on this. According to the spec a "role" is
more or less exactly what we think of as a "group", with the extension
that roles can have other roles as members (barring circularity).
In particular the spec draws a distinction between "user identifiers"
and "role identifiers", although this distinction seems very nearly 100%
useless because the two sorts of identifiers can be used almost
interchangeably (an "authorization identifier" means either one, and in
most places "authorization identifier" is what is relevant). AFAICT the
only really solid reason for the distinction is that you have to log in
initially as a user and not as a role. That strikes me as a security
policy --- it's analogous to saying you can't log in directly as root
but have to su to root from your personal login --- which may be a good
thing for a given site to enforce but IMHO it should not be hard-wired
into the security mechanism.

The implementation reason for not having a hard distinction is mainly
that we want to have a single unique-identifier space for both users and
roles. This simplifies representation of ACLs (which will no longer
need extra bits to identify whether an entry references a user or a
group) and allows us to have groups as members of other groups without
messy complication there.

It's not entirely clear to me whether the spec allows roles to be
directly owners of objects, but I think we should allow it.

So I'm envisioning something like

CREATE TABLE pg_role (
rolname name, -- name of role
rolsuper boolean, -- superuser?
rolcreateuser boolean, -- can create more users?
rolcreatedb boolean, -- can create databases?
rolcatupdate boolean, -- can hack system catalogs?
rolcanlogin boolean, -- can log in as this role?
rolvaliduntil timestamptz, -- password
rolpassword text, -- password expiration time
rolmembers oid[], -- OIDs of members, if any
roladmin boolean[], -- do members have ADMIN OPTION
rolconfig text[] -- ALTER USER SET guc = value
) WITH OIDS;

Some notes:

It might be better to call this by some name other than "pg_role",
since what it defines is not exactly roles in the sense that SQL99
uses; but I don't have a good idea what to use instead.
"pg_authorization" would work but it's unwieldy.

OIDs of rows in this table replace AclIds.

I'm supposing that we should separate "superuserness" from "can create
users" (presumably a non-superuser with rolcreateuser would only be
allowed to create non-super users). The lack of distinction on this
point has been a conceptual problem for newbies for a long time, and an
admin issue too. As long as we are hacking this table we should fix it.

If you want to enforce a hard distinction between users and roles (groups)
then you'd prohibit rolcanlogin from being true when rolmembers is
nonempty, but as said above I'm not sure the system should enforce that.

rolpassword, rolvaliduntil, and rolconfig are irrelevant if not rolcanlogin.

The roladmin[] bool array indicates whether members were granted
admission WITH ADMIN OPTION, which means they can grant membership to
others (analogous to WITH GRANT OPTION for individual privileges).
I'm not sure this is sufficient ... we may need to record who granted
membership to each member as well, in order to process revocation.

It might be better to lose the rolmembers/roladmin columns and instead
represent membership in a separate table, roughly

CREATE TABLE pg_role_members (
role oid,
member oid,
grantor oid,
admin_option bool,
primary key (role, member, grantor)
);

This is cleaner from a relational theory point of view but is probably
harder for the system to process. One advantage is that it is easier to
find out "which roles does user X belong to?" ... but I'm not sure we
care about making that fast.

One thing that needs to be thought about before going too far is exactly
how ACL rights testing will work, particularly in the face of roles
being members of other roles. That is the one performance-critical
operation that uses these data structures, so we ought to design around
making it fast.

Ok, I probably will. Should I be concerned with trying to make
'smallish' patches that build upon each other (ie: change to pg_role
first, then change AclId to Oid, or whatever) or will one larger patch
that takes care of it all be ok?

Smaller patches are easier to review, for sure. Also, you'll need to
coordinate with Alvaro's work on dependencies for global objects.

regards, tom lane

#9Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Tom Lane (#8)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

Stephan,

On Sun, Jan 23, 2005 at 03:14:04PM -0500, Tom Lane wrote:

Smaller patches are easier to review, for sure. Also, you'll need to
coordinate with Alvaro's work on dependencies for global objects.

If you want, I can send you the current patch so you can see what has
changed in it, maybe merge it with some work of yours for separate
submittal.

--
Alvaro Herrera (<alvherre[@]dcc.uchile.cl>)
"Siempre hay que alimentar a los dioses, aunque la tierra est� seca" (Orual)

#10Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#9)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

* Alvaro Herrera (alvherre@dcc.uchile.cl) wrote:

On Sun, Jan 23, 2005 at 03:14:04PM -0500, Tom Lane wrote:

Smaller patches are easier to review, for sure. Also, you'll need to
coordinate with Alvaro's work on dependencies for global objects.

If you want, I can send you the current patch so you can see what has
changed in it, maybe merge it with some work of yours for separate
submittal.

Yeah, I'd appriciate seeing it and seeing if/how much it conflicts with
what I'm working on. I'll probably be going back to scratch since there
isn't really anything worthwhile in my patch to build on working towards
what Tom's laid out. Not a problem though, it didn't take me very long
to code. :)

Thanks,

Stephen

#11Stephen Frost
sfrost@snowman.net
In reply to: Tom Lane (#8)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

Stephen Frost <sfrost@snowman.net> writes:

Ok. Can I get some help defining what the New Truth will look like
then? I understand users and groups pretty well but I'm not 100% sure
about roles.

I looked through SQL99 a bit (see 4.31 "Basic security model") and think

Ah, I was looking through SQL2003 recently, I don't think much has
changed in that area though.

I now have some handle on this. According to the spec a "role" is
more or less exactly what we think of as a "group", with the extension
that roles can have other roles as members (barring circularity).

Right, ok.

In particular the spec draws a distinction between "user identifiers"
and "role identifiers", although this distinction seems very nearly 100%
useless because the two sorts of identifiers can be used almost
interchangeably (an "authorization identifier" means either one, and in
most places "authorization identifier" is what is relevant). AFAICT the
only really solid reason for the distinction is that you have to log in
initially as a user and not as a role. That strikes me as a security
policy --- it's analogous to saying you can't log in directly as root
but have to su to root from your personal login --- which may be a good
thing for a given site to enforce but IMHO it should not be hard-wired
into the security mechanism.

Ok, I agree, though personally I don't like the idea of permitting
role-logins, but no need to have the security system force it.

The implementation reason for not having a hard distinction is mainly
that we want to have a single unique-identifier space for both users and
roles. This simplifies representation of ACLs (which will no longer
need extra bits to identify whether an entry references a user or a
group) and allows us to have groups as members of other groups without
messy complication there.

The other difference would seem to be that "user identifiers" can't be
granted to users whereas "role identifiers" can be. Following this,
"rolmembers" must be NULL if rolcanlogin is true, no? That breaks if
roles can log in though. Or should we just allow granting of "user
identifiers" to other users- but if we do should the user be permitted
to do that?

It's not entirely clear to me whether the spec allows roles to be
directly owners of objects, but I think we should allow it.

I agree, and in fact group/role ownership is what I'm specifically
interested in, though I'd like role support too and so I'm happy to
implement it along the way. :)

So I'm envisioning something like

CREATE TABLE pg_role (
rolname name, -- name of role
rolsuper boolean, -- superuser?
rolcreateuser boolean, -- can create more users?
rolcreatedb boolean, -- can create databases?
rolcatupdate boolean, -- can hack system catalogs?
rolcanlogin boolean, -- can log in as this role?
rolvaliduntil timestamptz, -- password
rolpassword text, -- password expiration time
rolmembers oid[], -- OIDs of members, if any
roladmin boolean[], -- do members have ADMIN OPTION
rolconfig text[] -- ALTER USER SET guc = value
) WITH OIDS;

It might be better to call this by some name other than "pg_role",
since what it defines is not exactly roles in the sense that SQL99
uses; but I don't have a good idea what to use instead.
"pg_authorization" would work but it's unwieldy.

Hmmm, I agree pg_role isn't quite right. pg_auth would be shorter than
pg_authorization, but it isn't intuitive what it is. How about
pg_ident? It's not users or roles, but it's identifiers. Perhaps
pg_authid?

OIDs of rows in this table replace AclIds.

ok.

I'm supposing that we should separate "superuserness" from "can create
users" (presumably a non-superuser with rolcreateuser would only be
allowed to create non-super users). The lack of distinction on this
point has been a conceptual problem for newbies for a long time, and an
admin issue too. As long as we are hacking this table we should fix it.

Agreed.

If you want to enforce a hard distinction between users and roles (groups)
then you'd prohibit rolcanlogin from being true when rolmembers is
nonempty, but as said above I'm not sure the system should enforce that.

Right, but there's still the issue of granting "users" to users.

rolpassword, rolvaliduntil, and rolconfig are irrelevant if not rolcanlogin.

Right.

The roladmin[] bool array indicates whether members were granted
admission WITH ADMIN OPTION, which means they can grant membership to
others (analogous to WITH GRANT OPTION for individual privileges).
I'm not sure this is sufficient ... we may need to record who granted
membership to each member as well, in order to process revocation.

I think we'll probably need to record who granted membership too, to
prevent circulation as well as revocation processing..

It might be better to lose the rolmembers/roladmin columns and instead
represent membership in a separate table, roughly

CREATE TABLE pg_role_members (
role oid,
member oid,
grantor oid,
admin_option bool,
primary key (role, member, grantor)
);

This is cleaner from a relational theory point of view but is probably
harder for the system to process. One advantage is that it is easier to
find out "which roles does user X belong to?" ... but I'm not sure we
care about making that fast.

I like this approach more.

One thing that needs to be thought about before going too far is exactly
how ACL rights testing will work, particularly in the face of roles
being members of other roles. That is the one performance-critical
operation that uses these data structures, so we ought to design around
making it fast.

I agree that it should be fast- but I think it should be possible to
implement it in such a way that if you don't make roles members of other
roles then you won't pay a performance penelty for the fact that we
support that ability. If you use it, it'll be a bit more expensive to
check permissions where you do.

Ok, I probably will. Should I be concerned with trying to make
'smallish' patches that build upon each other (ie: change to pg_role
first, then change AclId to Oid, or whatever) or will one larger patch
that takes care of it all be ok?

Smaller patches are easier to review, for sure. Also, you'll need to
coordinate with Alvaro's work on dependencies for global objects.

Right, ok. I'll look at what Alvaro's got and think about the approach
and milestones/patches to get from where we're at now to what we want.

Stephen

#12Peter Eisentraut
peter_e@gmx.net
In reply to: Stephen Frost (#11)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

Stephen Frost wrote:

The other difference would seem to be that "user identifiers" can't
be granted to users whereas "role identifiers" can be. Following
this, "rolmembers" must be NULL if rolcanlogin is true, no? That
breaks if roles can log in though. Or should we just allow granting
of "user identifiers" to other users- but if we do should the user be
permitted to do that?

If he has admin option on his own role, sure. But I suppose by default
we wouldn't.

One use case I see is if someone goes on vacation he can temporarily
grant the privileges held by his user account to others without
actually giving out the login data.

--
Peter Eisentraut
http://developer.postgresql.org/~petere/

#13Bruno Wolff III
bruno@wolff.to
In reply to: Tom Lane (#8)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

On Sun, Jan 23, 2005 at 15:14:04 -0500,
Tom Lane <tgl@sss.pgh.pa.us> wrote:

It's not entirely clear to me whether the spec allows roles to be
directly owners of objects, but I think we should allow it.

I aggree with this. This can simplify maintainance as members of a group
come and go.

#14Stephen Frost
sfrost@snowman.net
In reply to: Peter Eisentraut (#12)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

* Peter Eisentraut (peter_e@gmx.net) wrote:

If he has admin option on his own role, sure. But I suppose by default
we wouldn't.

One use case I see is if someone goes on vacation he can temporarily
grant the privileges held by his user account to others without
actually giving out the login data.

Alright. I've thought about this some more and I think I agree with it.
A user doesn't implicitly have all rights on his own oid, but I guess
that wasn't ever really the case anyway (can't give himself superuser
rights, etc). I'll begin working on this soon (possibly as soon as
Thursday evening) unless someone else has comments on it.

Thanks,

Stephen

#15Stephen Frost
sfrost@snowman.net
In reply to: Tom Lane (#8)
1 attachment(s)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

Stephen Frost <sfrost@snowman.net> writes:

Ok. Can I get some help defining what the New Truth will look like
then? I understand users and groups pretty well but I'm not 100% sure
about roles.

So I'm envisioning something like

[...]

It might be better to call this by some name other than "pg_role",

[...]

It might be better to lose the rolmembers/roladmin columns and instead
represent membership in a separate table, roughly

[...]

One thing that needs to be thought about before going too far is exactly
how ACL rights testing will work, particularly in the face of roles
being members of other roles. That is the one performance-critical
operation that uses these data structures, so we ought to design around
making it fast.

Alright, here's a patch against head which adds in the tables pg_authid
and pg_auth_members as described in your previous mail. I've gotten a
bit farther than this in terms of implementation but here's something
for people to comment on, if they'd like to.

I've been thinking about the performance issues some and have to admit
that I havn't really come to much of a solution. It seems to me that
there's two ways to come at the issue:

a) start from the user:
Search for useroid in pg_auth_members.member
For each returned role, search for that role in member column
Repeat until all roles the useroid is in have been found
[Note: This could possibly be done and stored per-user on connection,
but it would mean we'd have to have a mechanism to update it when
necessary, possibly instigated by the user, or just force them to
reconnect ala unix group membership]
Look through ACL list to see if the useroid has permission or if any
of the roles found do.

b) start from the ACL list:
Search for each roleoid in pg_auth_members.role
For each returned member, search for that member in role column
Upon member == useroid match is found check for permission, if
granted then stop, otherwise continue processing
Has the advantage that the search stops once it's been determined
that permission is there and doesn't require updating.

If we do the user-part-of-which-roles search upon connection I expect
'a' would be quite fast, obviously it has it's drawbacks though. If we
feel that's not acceptable then I'm thinking 'b' would probably be
faster given that the ACL list is probably generally small and we can
short-circuit. I'm afraid 'b' might still be too slow though, comments?
thoughts? better ideas?

Thanks,

Stephen

Attachments:

02_add_pg_auth_catalog.patchtext/plain; charset=us-asciiDownload
diff -uNr pgsql.1/src/include/catalog/pg_auth_members.h pgsql.2/src/include/catalog/pg_auth_members.h
--- pgsql.1/src/include/catalog/pg_auth_members.h	1969-12-31 19:00:00.000000000 -0500
+++ pgsql.2/src/include/catalog/pg_auth_members.h	2005-01-27 23:24:17.000000000 -0500
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_auth_members.h
+ *	  definition of the system "authorization identifier members" relation (pg_auth_members)
+ *	  along with the relation's initial contents.
+ *		  pg_group is now a public accessible view on pg_authid and pg_auth_members.
+ *
+ *
+ * Portions Copyright (c) 2005, PostgreSQL Global Development Group
+ *
+ * $Id$
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *		  WHENEVER the definition for pg_auth_members changes, the
+ *		  view creation of pg_shadow/pg_group must be changed in initdb.sh!
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTH_MEMBERS_H
+#define PG_AUTH_MEMBERS_H
+
+/* ----------------
+ *		pg_auth_members definition.  cpp turns this into
+ *		typedef struct FormData_pg_auth_members
+ * ----------------
+ */
+CATALOG(pg_auth_members) BOOTSTRAP BKI_SHARED_RELATION BKI_WITHOUT_OIDS
+{
+	Oid			role;
+	Oid			member;
+	Oid			grantor;
+	bool		admin_option
+} FormData_pg_auth_members;
+
+/* ----------------
+ *		Form_pg_authid corresponds to a pointer to a tuple with
+ *		the format of pg_authid relation.
+ * ----------------
+ */
+typedef FormData_pg_auth_members *Form_pg_auth_members;
+
+/* ----------------
+ *		compiler constants for pg_auth_members
+ * ----------------
+ */
+#define Natts_pg_auth_members				4
+#define Anum_pg_auth_members_role			1
+#define Anum_pg_auth_members_member			2
+#define Anum_pg_auth_members_grantor		3
+#define Anum_pg_auth_members_admin_option			4
+
+#endif   /* PG_AUTH_MEMBERS_H */
diff -uNr pgsql.1/src/include/catalog/pg_authid.h pgsql.2/src/include/catalog/pg_authid.h
--- pgsql.1/src/include/catalog/pg_authid.h	1969-12-31 19:00:00.000000000 -0500
+++ pgsql.2/src/include/catalog/pg_authid.h	2005-01-27 23:24:03.000000000 -0500
@@ -0,0 +1,81 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_authid.h
+ *	  definition of the system "authorization identifier" relation (pg_authid)
+ *	  along with the relation's initial contents.
+ *		  pg_shadow is now a public accessible view on pg_authid.
+ *		  pg_group is now a public accessible view on pg_authid.
+ *
+ *
+ * Portions Copyright (c) 2005, PostgreSQL Global Development Group
+ *
+ * $Id$
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *		  WHENEVER the definition for pg_authid changes, the
+ *		  view creation of pg_shadow/pg_group must be changed in initdb.sh!
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTHID_H
+#define PG_AUTHID_H
+
+#include "utils/timestamp.h"
+
+/* ----------------
+ *		pg_authid definition.  cpp turns this into
+ *		typedef struct FormData_pg_authid
+ * ----------------
+ */
+CATALOG(pg_authid) BOOTSTRAP BKI_SHARED_RELATION
+{
+	NameData	rolname;
+	bool		rolsuper;		/* read this field via superuser() only */
+	bool		rolcreateuser;
+	bool		rolcreatedb;
+	bool		rolcatupdate;
+	bool		rolcanlogin;
+
+	/* remaining fields may be null; use heap_getattr to read them! */
+	TimestampTz		rolvaliduntil;		/* actually abstime */
+	text		rolpassword;
+	text		rolconfig[1];
+} FormData_pg_authid;
+
+/* ----------------
+ *		Form_pg_authid corresponds to a pointer to a tuple with
+ *		the format of pg_authid relation.
+ * ----------------
+ */
+typedef FormData_pg_authid *Form_pg_authid;
+
+/* ----------------
+ *		compiler constants for pg_authid
+ * ----------------
+ */
+#define Natts_pg_authid				9
+#define Anum_pg_authid_rolname			1
+#define Anum_pg_authid_rolsuper			2
+#define Anum_pg_authid_rolcreateuser		3
+#define Anum_pg_authid_rolcreatedb			4
+#define Anum_pg_authid_rolcatupdate		5
+#define Anum_pg_authid_rolcanlogin		6
+#define Anum_pg_authid_rolvaliduntil			7
+#define Anum_pg_authid_rolpassword			8
+#define Anum_pg_authid_rolconfig		9
+
+/* ----------------
+ *		initial contents of pg_authid
+ *
+ * The uppercase quantities will be replaced at initdb time with
+ * user choices.
+ * ----------------
+ */
+DATA(insert OID = 142 ( "POSTGRES" t t t t t _null_ _null_ _null_ ));
+
+#define SUPERUSEROID 142
+
+#endif   /* PG_AUTHID_H */
#16Bort, Paul
pbort@tmwsystems.com
In reply to: Stephen Frost (#15)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

a) start from the user:
Search for useroid in pg_auth_members.member
For each returned role, search for that role in member column
Repeat until all roles the useroid is in have been found
[Note: This could possibly be done and stored per-user on
connection,
but it would mean we'd have to have a mechanism to update it when
necessary, possibly instigated by the user, or just force them to
reconnect ala unix group membership]
Look through ACL list to see if the useroid has permission
or if any
of the roles found do.

b) start from the ACL list:
Search for each roleoid in pg_auth_members.role
For each returned member, search for that member in role column
Upon member == useroid match is found check for permission, if
granted then stop, otherwise continue processing
Has the advantage that the search stops once it's been determined
that permission is there and doesn't require updating.

If I may humbly suggest another option:

c) Use tables for users, roles, and user x role as you already have
(Or was a user's roles in an array? I forget)
Add a fourth table (access?) with the PK (user, priv, role).
Whenever a privilege is granted or revoked, for a user or a role,
insert or delete the appropriate rows in the access table.
This pre-loads all of the cost of maintaining the ACL and should
reduce the effort of checking a particular privilege to an index
seek.

With this method, a user can be granted a privilege by more than one role,
and if they are removed from one of those roles, the other still grants
the privilege. The access table can also store the privileges that each
role has by storing the role ID in the user ID column.

I know that it makes for a potentially huge table, but it makes the model
straightforward and reliable.

Examples:

Grant role 'foo' privilege 'bar':
INSERT INTO access (user, priv, role ) VALUES ( 'foo', 'bar', 'foo' );

Grant user 'baz' role 'foo':
INSERT INTO access ( user, priv, role )
SELECT 'baz', priv, role FROM access WHERE user = 'foo';

Remove user 'baz' from role 'foo':
DELETE FROM access WHERE user = 'baz' AND role = 'foo';

Remove privilege 'bar' from role 'foo':
DELETE FROM access WHERE priv = 'bar' AND role = 'foo';
-- Note that this automatically cleaned up all of the users, too.

Grant privilege 'bar' to user 'baz' without a role involved:
INSERT INTO access ( user, priv, role ) VALUES ( 'baz', 'bar', 'baz' );

Grant user 'postgres' privilege 'su' in a hard-to-revoke way:
INSERT INTO access ( user, priv, role ) VALUES ( 'postgres', 'su', '' );

Check to see if user 'baz' has privilege 'bar':
SELECT user, priv, role FROM access WHERE user = 'baz' AND priv = 'bar';
-- This even tells you the role(s) that grant the privilege.

Inheritance from role to role can even be handled by repeating the inserts
or deletes with appropriate roles. (This would even allow a role to inherit
a privilege from multiple parent roles, and work correctly if it is revoked
by one.)

#17Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephen Frost (#15)
Re: [PATCHES] Merge pg_shadow && pg_group -- UNTESTED

Stephen Frost <sfrost@snowman.net> writes:

I've been thinking about the performance issues some and have to admit
that I havn't really come to much of a solution. It seems to me that
there's two ways to come at the issue:

a) start from the user:
...
b) start from the ACL list:
...

The current ACL-checking code does (b), relying on a function in_group()
that tests whether the target userid is a member of a given group.
I would suggest preserving this basic structure if only to avoid breaking
things unintentionally. However, you could think about caching the set
of groups that a user belongs to, thus combining the best features of
both (a) and (b). It's always bothered me that in_group() seemed like a
fairly expensive operation.

[Note: This could possibly be done and stored per-user on connection,
but it would mean we'd have to have a mechanism to update it when
necessary, possibly instigated by the user, or just force them to
reconnect ala unix group membership]

No, we'd drive it off syscache invalidation. Any change in pg_auth_members
would cause us to just discard the whole membership cache. This sort of
mechanism is already in use in a couple places (schema search list
maintenance is one example IIRC --- look at namespace.c).

regards, tom lane