]> granicus.if.org Git - postgresql/blobdiff - src/backend/commands/user.c
Stamp copyrights for year 2011.
[postgresql] / src / backend / commands / user.c
index a54162e24c5446cbc9f4bdb602615c831c1911c3..9e505b7525f88f695b46fab22955b5fd18a40937 100644 (file)
 /*-------------------------------------------------------------------------
  *
  * user.c
- *       Commands for manipulating users and groups.
+ *       Commands for manipulating roles (formerly called users).
  *
- * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.119 2003/07/18 23:20:32 tgl Exp $
+ * src/backend/commands/user.c
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <unistd.h>
-
+#include "access/genam.h"
 #include "access/heapam.h"
-#include "catalog/catname.h"
+#include "access/xact.h"
+#include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_auth_members.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
-#include "catalog/pg_group.h"
-#include "catalog/pg_shadow.h"
-#include "catalog/pg_type.h"
+#include "catalog/pg_db_role_setting.h"
+#include "commands/comment.h"
+#include "commands/dbcommands.h"
 #include "commands/user.h"
-#include "libpq/crypt.h"
+#include "libpq/md5.h"
 #include "miscadmin.h"
-#include "storage/pmsignal.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
-#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
-#include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "utils/tqual.h"
 
 
-#define PWD_FILE               "pg_pwd"
-#define USER_GROUP_FILE "pg_group"
-
-
+/* GUC parameter */
 extern bool Password_encryption;
 
-static bool user_file_update_needed = false;
-static bool group_file_update_needed = false;
-
-
-static void CheckPgUserAclNotNull(void);
-static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
-                                         List *members);
-static IdList *IdListToArray(List *members);
-static List *IdArrayToList(IdList *oldarray);
-
-
-/*
- *     fputs_quote
- *
- *     Outputs string in quotes, with double-quotes duplicated.
- *     We could use quote_ident(), but that expects a TEXT argument.
- */
-static void
-fputs_quote(char *str, FILE *fp)
-{
-       fputc('"', fp);
-       while (*str)
-       {
-               fputc(*str, fp);
-               if (*str == '"')
-                       fputc('"', fp);
-               str++;
-       }
-       fputc('"', fp);
-}
-
-
-/*
- * 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.
- */
-char *
-user_getfilename(void)
-{
-       int                     bufsize;
-       char       *pfnam;
-
-       bufsize = strlen(DataDir) + strlen("/global/") +
-               strlen(PWD_FILE) + 1;
-       pfnam = (char *) palloc(bufsize);
-       snprintf(pfnam, bufsize, "%s/global/%s", DataDir, PWD_FILE);
-
-       return pfnam;
-}
-
-
-/*
- * 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("unable to write temp 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 illegal characters in the group name.
-                */
-               i = strcspn(groname, "\n");
-               if (groname[i] != '\0')
-               {
-                       elog(LOG, "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')
-                               {
-                                       elog(LOG, "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);
-
-       fflush(fp);
-       if (ferror(fp))
-               ereport(ERROR,
-                               (errcode_for_file_access(),
-                                errmsg("unable to write temp file \"%s\": %m", tempname)));
-       FreeFile(fp);
-
-       /*
-        * 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 \"%s\" to \"%s\": %m",
-                                               tempname, filename)));
-
-       pfree((void *) tempname);
-       pfree((void *) filename);
-}
-
-
-/*
- * write_user_file: update the flat password file
- */
-static void
-write_user_file(Relation urel)
-{
-       char       *filename,
-                          *tempname;
-       int                     bufsize;
-       FILE       *fp;
-       mode_t          oumask;
-       HeapScanDesc scan;
-       HeapTuple       tuple;
-       TupleDesc       dsc = RelationGetDescr(urel);
-
-       /*
-        * Create a temporary filename to be renamed later.  This prevents the
-        * backend from clobbering the pg_pwd file while the postmaster might
-        * be reading from it.
-        */
-       filename = user_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("unable to write temp file \"%s\": %m", tempname)));
-
-       /*
-        * Read pg_shadow 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(urel, SnapshotSelf, 0, NULL);
-       while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
-       {
-               Datum           datum;
-               bool            isnull;
-               char       *usename,
-                                  *passwd,
-                                  *valuntil;
-               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.
-                */
-               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 username and password.
-                */
-               i = strcspn(usename, "\n");
-               if (usename[i] != '\0')
-               {
-                       elog(LOG, "invalid user name \"%s\"", usename);
-                       continue;
-               }
-               i = strcspn(passwd, "\n");
-               if (passwd[i] != '\0')
-               {
-                       elog(LOG, "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(usename, fp);
-               fputs(" ", fp);
-               fputs_quote(passwd, fp);
-               fputs(" ", fp);
-               fputs_quote(valuntil, fp);
-               fputs("\n", fp);
-
-               pfree(passwd);
-               pfree(valuntil);
-       }
-       heap_endscan(scan);
-
-       fflush(fp);
-       if (ferror(fp))
-               ereport(ERROR,
-                               (errcode_for_file_access(),
-                                errmsg("unable to write temp file \"%s\": %m", tempname)));
-       FreeFile(fp);
-
-       /*
-        * 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 \"%s\" to \"%s\": %m",
-                                               tempname, filename)));
-
-       pfree((void *) tempname);
-       pfree((void *) filename);
-}
-
-
-/*
- * This trigger is fired whenever someone modifies pg_shadow or pg_group
- * via general-purpose INSERT/UPDATE/DELETE commands.
- *
- * XXX should probably have two separate triggers.
- */
-Datum
-update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS)
-{
-       user_file_update_needed = true;
-       group_file_update_needed = true;
+/* Hook to check passwords in CreateRole() and AlterRole() */
+check_password_hook_type check_password_hook = NULL;
 
-       return PointerGetDatum(NULL);
-}
+static List *roleNamesToIds(List *memberNames);
+static void AddRoleMems(const char *rolename, Oid roleid,
+                       List *memberNames, List *memberIds,
+                       Oid grantorId, bool admin_opt);
+static void DelRoleMems(const char *rolename, Oid roleid,
+                       List *memberNames, List *memberIds,
+                       bool admin_opt);
 
 
-/*
- * This routine is called during transaction commit or abort.
- *
- * On commit, if we've written pg_shadow or pg_group during the current
- * transaction, update the flat files and signal the postmaster.
- *
- * On abort, just reset the static flags so we don't try to do it on the
- * next successful commit.
- *
- * NB: this should be the last step before actual transaction commit.
- * If any error aborts the transaction after we run this code, the postmaster
- * will still have received and cached the changed data; so minimize the
- * window for such problems.
- */
-void
-AtEOXact_UpdatePasswordFile(bool isCommit)
+/* Check if current user has createrole privileges */
+static bool
+have_createrole_privilege(void)
 {
-       Relation        urel = NULL;
-       Relation        grel = NULL;
-
-       if (! (user_file_update_needed || group_file_update_needed))
-               return;
-
-       if (! isCommit)
-       {
-               user_file_update_needed = false;
-               group_file_update_needed = false;
-               return;
-       }
+       bool            result = false;
+       HeapTuple       utup;
 
-       /*
-        * We use ExclusiveLock to ensure that only one backend writes the flat
-        * file(s) at a time.  That's sufficient because it's okay to allow plain
-        * reads of the tables in parallel.  There is some chance of a deadlock
-        * here (if we were triggered by a user update of pg_shadow or pg_group,
-        * which likely won't have gotten a strong enough lock), so get the locks
-        * we need before writing anything.
-        */
-       if (user_file_update_needed)
-               urel = heap_openr(ShadowRelationName, ExclusiveLock);
-       if (group_file_update_needed)
-               grel = heap_openr(GroupRelationName, ExclusiveLock);
-
-       /* Okay to write the files */
-       if (user_file_update_needed)
-       {
-               user_file_update_needed = false;
-               write_user_file(urel);
-               heap_close(urel, NoLock);
-       }
+       /* Superusers can always do everything */
+       if (superuser())
+               return true;
 
-       if (group_file_update_needed)
+       utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId()));
+       if (HeapTupleIsValid(utup))
        {
-               group_file_update_needed = false;
-               write_group_file(grel);
-               heap_close(grel, NoLock);
+               result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole;
+               ReleaseSysCache(utup);
        }
-
-       /*
-        * Signal the postmaster to reload its password & group-file cache.
-        */
-       SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE);
+       return result;
 }
 
 
-
 /*
- * CREATE USER
+ * CREATE ROLE
  */
 void
-CreateUser(CreateUserStmt *stmt)
+CreateRole(CreateRoleStmt *stmt)
 {
-       Relation        pg_shadow_rel;
-       TupleDesc       pg_shadow_dsc;
-       HeapScanDesc scan;
+       Relation        pg_authid_rel;
+       TupleDesc       pg_authid_dsc;
        HeapTuple       tuple;
-       Datum           new_record[Natts_pg_shadow];
-       char            new_record_nulls[Natts_pg_shadow];
-       bool            user_exists = false,
-                               sysid_exists = false,
-                               havesysid = false;
-       int                     max_id;
-       List       *item,
-                          *option;
-       char       *password = NULL;    /* PostgreSQL user password */
+       Datum           new_record[Natts_pg_authid];
+       bool            new_record_nulls[Natts_pg_authid];
+       Oid                     roleid;
+       ListCell   *item;
+       ListCell   *option;
+       char       *password = NULL;    /* user password */
        bool            encrypt_password = Password_encryption; /* encrypt password? */
        char            encrypted_password[MD5_PASSWD_LEN + 1];
-       int                     sysid = 0;              /* PgSQL system id (valid if havesysid) */
+       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            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
-                                                                                * until */
+       bool            canlogin = false;               /* Can this user login? */
+       bool            isreplication = false; /* Is this a replication role? */
+       int                     connlimit = -1; /* maximum connections allowed */
+       List       *addroleto = NIL;    /* roles to make this a member of */
+       List       *rolemembers = NIL;          /* roles to be members of this role */
+       List       *adminmembers = NIL;         /* roles to be admins of this role */
+       char       *validUntil = NULL;          /* time the login is valid until */
+       Datum           validUntil_datum;               /* same, as timestamptz Datum */
+       bool            validUntil_null;
        DefElem    *dpassword = NULL;
-       DefElem    *dsysid = NULL;
+       DefElem    *dissuper = NULL;
+       DefElem    *dinherit = NULL;
+       DefElem    *dcreaterole = NULL;
        DefElem    *dcreatedb = NULL;
-       DefElem    *dcreateuser = NULL;
-       DefElem    *dgroupElts = NULL;
+       DefElem    *dcanlogin = NULL;
+       DefElem    *disreplication = NULL;
+       DefElem    *dconnlimit = NULL;
+       DefElem    *daddroleto = NULL;
+       DefElem    *drolemembers = NULL;
+       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)
        {
@@ -527,11 +149,32 @@ CreateUser(CreateUserStmt *stmt)
                }
                else if (strcmp(defel->defname, "sysid") == 0)
                {
-                       if (dsysid)
+                       ereport(NOTICE,
+                                       (errmsg("SYSID can no longer be specified")));
+               }
+               else if (strcmp(defel->defname, "superuser") == 0)
+               {
+                       if (dissuper)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                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)
                                ereport(ERROR,
                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                 errmsg("conflicting or redundant options")));
-                       dsysid = defel;
+                       dcreaterole = defel;
                }
                else if (strcmp(defel->defname, "createdb") == 0)
                {
@@ -541,21 +184,53 @@ CreateUser(CreateUserStmt *stmt)
                                                 errmsg("conflicting or redundant options")));
                        dcreatedb = defel;
                }
-               else if (strcmp(defel->defname, "createuser") == 0)
+               else if (strcmp(defel->defname, "canlogin") == 0)
                {
-                       if (dcreateuser)
+                       if (dcanlogin)
                                ereport(ERROR,
                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                 errmsg("conflicting or redundant options")));
-                       dcreateuser = defel;
+                       dcanlogin = defel;
                }
-               else if (strcmp(defel->defname, "groupElts") == 0)
+               else if (strcmp(defel->defname, "isreplication") == 0)
                {
-                       if (dgroupElts)
+                       if (disreplication)
                                ereport(ERROR,
                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                 errmsg("conflicting or redundant options")));
-                       dgroupElts = defel;
+                       disreplication = defel;
+               }
+               else if (strcmp(defel->defname, "connectionlimit") == 0)
+               {
+                       if (dconnlimit)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("conflicting or redundant options")));
+                       dconnlimit = defel;
+               }
+               else if (strcmp(defel->defname, "addroleto") == 0)
+               {
+                       if (daddroleto)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("conflicting or redundant options")));
+                       daddroleto = defel;
+               }
+               else if (strcmp(defel->defname, "rolemembers") == 0)
+               {
+                       if (drolemembers)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("conflicting or redundant options")));
+                       drolemembers = defel;
+               }
+               else if (strcmp(defel->defname, "adminmembers") == 0)
+               {
+                       if (dadminmembers)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("conflicting or redundant options")));
+                       dadminmembers = defel;
                }
                else if (strcmp(defel->defname, "validUntil") == 0)
                {
@@ -570,191 +245,237 @@ CreateUser(CreateUserStmt *stmt)
                                 defel->defname);
        }
 
+       if (dpassword && dpassword->arg)
+               password = strVal(dpassword->arg);
+       if (dissuper)
+               issuper = intVal(dissuper->arg) != 0;
+       if (dinherit)
+               inherit = intVal(dinherit->arg) != 0;
+       if (dcreaterole)
+               createrole = intVal(dcreaterole->arg) != 0;
        if (dcreatedb)
                createdb = intVal(dcreatedb->arg) != 0;
-       if (dcreateuser)
-               createuser = intVal(dcreateuser->arg) != 0;
-       if (dsysid)
+       if (dcanlogin)
+               canlogin = intVal(dcanlogin->arg) != 0;
+       if (disreplication)
+               isreplication = intVal(disreplication->arg) != 0;
+       if (dconnlimit)
        {
-               sysid = intVal(dsysid->arg);
-               if (sysid <= 0)
+               connlimit = intVal(dconnlimit->arg);
+               if (connlimit < -1)
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                        errmsg("user id must be positive")));
-               havesysid = true;
+                                        errmsg("invalid connection limit: %d", connlimit)));
        }
+       if (daddroleto)
+               addroleto = (List *) daddroleto->arg;
+       if (drolemembers)
+               rolemembers = (List *) drolemembers->arg;
+       if (dadminmembers)
+               adminmembers = (List *) dadminmembers->arg;
        if (dvalidUntil)
                validUntil = strVal(dvalidUntil->arg);
-       if (dpassword)
-               password = strVal(dpassword->arg);
-       if (dgroupElts)
-               groupElts = (List *) dgroupElts->arg;
 
        /* Check some permissions first */
-       if (password)
-               CheckPgUserAclNotNull();
-
-       if (!superuser())
-               ereport(ERROR,
-                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("permission denied")));
+       if (issuper)
+       {
+               if (!superuser())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must be superuser to create superusers")));
+       }
+       else if (isreplication)
+       {
+               if (!superuser())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must be superuser to create replication users")));
+       }
+       else
+       {
+               if (!have_createrole_privilege())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("permission denied to create role")));
+       }
 
-       if (strcmp(stmt->user, "public") == 0)
+       if (strcmp(stmt->role, "public") == 0 ||
+               strcmp(stmt->role, "none") == 0)
                ereport(ERROR,
                                (errcode(ERRCODE_RESERVED_NAME),
-                                errmsg("user name \"%s\" is reserved",
-                                               stmt->user)));
+                                errmsg("role name \"%s\" is reserved",
+                                               stmt->role)));
 
        /*
-        * Scan the pg_shadow relation to be certain the user or id doesn't
-        * already exist.  Note we secure exclusive lock, because we also need
-        * to be sure of what the next usesysid should be, and we need to
-        * protect our eventual update of the flat password file.
+        * Check the pg_authid relation to be certain the role doesn't already
+        * exist.
         */
-       pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
-       pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
-
-       scan = heap_beginscan(pg_shadow_rel, SnapshotNow, 0, NULL);
-       max_id = 99;                            /* start auto-assigned ids at 100 */
-       while (!user_exists && !sysid_exists &&
-                  (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
-       {
-               Form_pg_shadow shadow_form = (Form_pg_shadow) GETSTRUCT(tuple);
-               int32           this_sysid;
-
-               user_exists = (strcmp(NameStr(shadow_form->usename), stmt->user) == 0);
-
-               this_sysid = shadow_form->usesysid;
-               if (havesysid)                  /* customized id wanted */
-                       sysid_exists = (this_sysid == sysid);
-               else
-               {
-                       /* pick 1 + max */
-                       if (this_sysid > max_id)
-                               max_id = this_sysid;
-               }
-       }
-       heap_endscan(scan);
+       pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
+       pg_authid_dsc = RelationGetDescr(pg_authid_rel);
 
-       if (user_exists)
+       if (OidIsValid(get_role_oid(stmt->role, true)))
                ereport(ERROR,
                                (errcode(ERRCODE_DUPLICATE_OBJECT),
-                                errmsg("user \"%s\" already exists",
-                                               stmt->user)));
-       if (sysid_exists)
-               ereport(ERROR,
-                               (errcode(ERRCODE_DUPLICATE_OBJECT),
-                                errmsg("sysid %d is already assigned", sysid)));
+                                errmsg("role \"%s\" already exists",
+                                               stmt->role)));
+
+       /* Convert validuntil to internal form */
+       if (validUntil)
+       {
+               validUntil_datum = DirectFunctionCall3(timestamptz_in,
+                                                                                          CStringGetDatum(validUntil),
+                                                                                          ObjectIdGetDatum(InvalidOid),
+                                                                                          Int32GetDatum(-1));
+               validUntil_null = false;
+       }
+       else
+       {
+               validUntil_datum = (Datum) 0;
+               validUntil_null = true;
+       }
 
-       /* If no sysid given, use max existing id + 1 */
-       if (!havesysid)
-               sysid = max_id + 1;
+       /*
+        * Call the password checking hook if there is one defined
+        */
+       if (check_password_hook && password)
+               (*check_password_hook) (stmt->role,
+                                                               password,
+                          isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+                                                               validUntil_datum,
+                                                               validUntil_null);
 
        /*
         * Build a tuple to insert
         */
        MemSet(new_record, 0, sizeof(new_record));
-       MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
-
-       new_record[Anum_pg_shadow_usename - 1] =
-               DirectFunctionCall1(namein, CStringGetDatum(stmt->user));
-       new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(sysid);
-       AssertState(BoolIsValid(createdb));
-       new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb);
-       AssertState(BoolIsValid(createuser));
-       new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser);
-       /* superuser gets catupd right by default */
-       new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser);
+       MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+
+       new_record[Anum_pg_authid_rolname - 1] =
+               DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
+
+       new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
+       new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
+       new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
+       new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
+       /* superuser gets catupdate right by default */
+       new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper);
+       new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
+       new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
+       new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
 
        if (password)
        {
                if (!encrypt_password || isMD5(password))
-                       new_record[Anum_pg_shadow_passwd - 1] =
-                               DirectFunctionCall1(textin, CStringGetDatum(password));
+                       new_record[Anum_pg_authid_rolpassword - 1] =
+                               CStringGetTextDatum(password);
                else
                {
-                       if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
-                                                       encrypted_password))
+                       if (!pg_md5_encrypt(password, stmt->role, strlen(stmt->role),
+                                                               encrypted_password))
                                elog(ERROR, "password encryption failed");
-                       new_record[Anum_pg_shadow_passwd - 1] =
-                               DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
+                       new_record[Anum_pg_authid_rolpassword - 1] =
+                               CStringGetTextDatum(encrypted_password);
                }
        }
        else
-               new_record_nulls[Anum_pg_shadow_passwd - 1] = 'n';
-
-       if (validUntil)
-               new_record[Anum_pg_shadow_valuntil - 1] =
-                       DirectFunctionCall1(abstimein, CStringGetDatum(validUntil));
-       else
-               new_record_nulls[Anum_pg_shadow_valuntil - 1] = 'n';
+               new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
 
-       new_record_nulls[Anum_pg_shadow_useconfig - 1] = 'n';
+       new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
+       new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
 
-       tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
+       tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
 
        /*
-        * Insert new record in the pg_shadow table
+        * Insert new record in the pg_authid table
         */
-       simple_heap_insert(pg_shadow_rel, tuple);
+       roleid = simple_heap_insert(pg_authid_rel, tuple);
+       CatalogUpdateIndexes(pg_authid_rel, tuple);
 
-       /* Update indexes */
-       CatalogUpdateIndexes(pg_shadow_rel, tuple);
+       /*
+        * Advance command counter so we can see new record; else tests in
+        * AddRoleMems may fail.
+        */
+       if (addroleto || adminmembers || rolemembers)
+               CommandCounterIncrement();
 
        /*
-        * Add the user to the groups specified. We'll just call the below
-        * AlterGroup for this.
+        * Add the new role to the specified existing roles.
         */
-       foreach(item, groupElts)
+       foreach(item, addroleto)
        {
-               AlterGroupStmt ags;
+               char       *oldrolename = strVal(lfirst(item));
+               Oid                     oldroleid = get_role_oid(oldrolename, false);
 
-               ags.name = strVal(lfirst(item));                /* the group name to add
-                                                                                                * this in */
-               ags.action = +1;
-               ags.listUsers = makeList1(makeInteger(sysid));
-               AlterGroup(&ags, "CREATE USER");
+               AddRoleMems(oldrolename, oldroleid,
+                                       list_make1(makeString(stmt->role)),
+                                       list_make1_oid(roleid),
+                                       GetUserId(), false);
        }
 
        /*
-        * Now we can clean up; but keep lock until commit (to avoid possible
-        * deadlock when commit code tries to acquire lock).
+        * Add the specified members to this new role. adminmembers get the admin
+        * option, rolemembers don't.
         */
-       heap_close(pg_shadow_rel, NoLock);
+       AddRoleMems(stmt->role, roleid,
+                               adminmembers, roleNamesToIds(adminmembers),
+                               GetUserId(), true);
+       AddRoleMems(stmt->role, roleid,
+                               rolemembers, roleNamesToIds(rolemembers),
+                               GetUserId(), false);
+
+       /* Post creation hook for new role */
+       InvokeObjectAccessHook(OAT_POST_CREATE, AuthIdRelationId, roleid, 0);
 
        /*
-        * Set flag to update flat password file at commit.
+        * Close pg_authid, but keep lock till commit.
         */
-       user_file_update_needed = true;
+       heap_close(pg_authid_rel, NoLock);
 }
 
 
-
 /*
- * ALTER USER
+ * ALTER ROLE
+ *
+ * Note: the rolemembers option accepted here is intended to support the
+ * backwards-compatible ALTER GROUP syntax.  Although it will work to say
+ * "ALTER ROLE role ROLE rolenames", we don't document it.
  */
 void
-AlterUser(AlterUserStmt *stmt)
+AlterRole(AlterRoleStmt *stmt)
 {
-       Datum           new_record[Natts_pg_shadow];
-       char            new_record_nulls[Natts_pg_shadow];
-       char            new_record_repl[Natts_pg_shadow];
-       Relation        pg_shadow_rel;
-       TupleDesc       pg_shadow_dsc;
+       Datum           new_record[Natts_pg_authid];
+       bool            new_record_nulls[Natts_pg_authid];
+       bool            new_record_repl[Natts_pg_authid];
+       Relation        pg_authid_rel;
+       TupleDesc       pg_authid_dsc;
        HeapTuple       tuple,
                                new_tuple;
-       List       *option;
-       char       *password = NULL;    /* PostgreSQL user password */
+       ListCell   *option;
+       char       *password = NULL;    /* user password */
        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                     createuser = -1;        /* Can this user create users? */
-       char       *validUntil = NULL;          /* The time the login is valid
-                                                                                * until */
+       int                     canlogin = -1;  /* Can this user login? */
+       int                     isreplication = -1; /* Is this a replication role? */
+       int                     connlimit = -1; /* maximum connections allowed */
+       List       *rolemembers = NIL;          /* roles to be added/removed */
+       char       *validUntil = NULL;          /* time the login is valid until */
+       Datum           validUntil_datum;               /* same, as timestamptz Datum */
+       bool            validUntil_null;
        DefElem    *dpassword = NULL;
+       DefElem    *dissuper = NULL;
+       DefElem    *dinherit = NULL;
+       DefElem    *dcreaterole = NULL;
        DefElem    *dcreatedb = NULL;
-       DefElem    *dcreateuser = NULL;
+       DefElem    *dcanlogin = NULL;
+       DefElem    *disreplication = NULL;
+       DefElem    *dconnlimit = NULL;
+       DefElem    *drolemembers = NULL;
        DefElem    *dvalidUntil = NULL;
+       Oid                     roleid;
 
        /* Extract options from the statement node tree */
        foreach(option, stmt->options)
@@ -775,6 +496,30 @@ AlterUser(AlterUserStmt *stmt)
                        else if (strcmp(defel->defname, "unencryptedPassword") == 0)
                                encrypt_password = false;
                }
+               else if (strcmp(defel->defname, "superuser") == 0)
+               {
+                       if (dissuper)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                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)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("conflicting or redundant options")));
+                       dcreaterole = defel;
+               }
                else if (strcmp(defel->defname, "createdb") == 0)
                {
                        if (dcreatedb)
@@ -783,970 +528,995 @@ AlterUser(AlterUserStmt *stmt)
                                                 errmsg("conflicting or redundant options")));
                        dcreatedb = defel;
                }
-               else if (strcmp(defel->defname, "createuser") == 0)
+               else if (strcmp(defel->defname, "canlogin") == 0)
                {
-                       if (dcreateuser)
+                       if (dcanlogin)
                                ereport(ERROR,
                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                 errmsg("conflicting or redundant options")));
-                       dcreateuser = defel;
+                       dcanlogin = defel;
                }
-               else if (strcmp(defel->defname, "validUntil") == 0)
+               else if (strcmp(defel->defname, "isreplication") == 0)
                {
-                       if (dvalidUntil)
+                       if (disreplication)
                                ereport(ERROR,
                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                 errmsg("conflicting or redundant options")));
-                       dvalidUntil = defel;
+                       disreplication = defel;
                }
-               else
-                       elog(ERROR, "option \"%s\" not recognized",
-                                defel->defname);
-       }
-
-       if (dcreatedb)
-               createdb = intVal(dcreatedb->arg);
-       if (dcreateuser)
-               createuser = intVal(dcreateuser->arg);
-       if (dvalidUntil)
-               validUntil = strVal(dvalidUntil->arg);
-       if (dpassword)
-               password = strVal(dpassword->arg);
-
-       if (password)
-               CheckPgUserAclNotNull();
-
-       /* must be superuser or just want to change your own password */
-       if (!superuser() &&
-               !(createdb < 0 &&
-                 createuser < 0 &&
-                 !validUntil &&
-                 password &&
-                 strcmp(GetUserNameFromId(GetUserId()), stmt->user) == 0))
-               ereport(ERROR,
-                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("permission denied")));
+               else if (strcmp(defel->defname, "connectionlimit") == 0)
+               {
+                       if (dconnlimit)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("conflicting or redundant options")));
+                       dconnlimit = defel;
+               }
+               else if (strcmp(defel->defname, "rolemembers") == 0 &&
+                                stmt->action != 0)
+               {
+                       if (drolemembers)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("conflicting or redundant options")));
+                       drolemembers = defel;
+               }
+               else if (strcmp(defel->defname, "validUntil") == 0)
+               {
+                       if (dvalidUntil)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("conflicting or redundant options")));
+                       dvalidUntil = defel;
+               }
+               else
+                       elog(ERROR, "option \"%s\" not recognized",
+                                defel->defname);
+       }
+
+       if (dpassword && dpassword->arg)
+               password = strVal(dpassword->arg);
+       if (dissuper)
+               issuper = intVal(dissuper->arg);
+       if (dinherit)
+               inherit = intVal(dinherit->arg);
+       if (dcreaterole)
+               createrole = intVal(dcreaterole->arg);
+       if (dcreatedb)
+               createdb = intVal(dcreatedb->arg);
+       if (dcanlogin)
+               canlogin = intVal(dcanlogin->arg);
+       if (disreplication)
+               isreplication = intVal(disreplication->arg);
+       if (dconnlimit)
+       {
+               connlimit = intVal(dconnlimit->arg);
+               if (connlimit < -1)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("invalid connection limit: %d", connlimit)));
+       }
+       if (drolemembers)
+               rolemembers = (List *) drolemembers->arg;
+       if (dvalidUntil)
+               validUntil = strVal(dvalidUntil->arg);
 
        /*
-        * Scan the pg_shadow relation to be certain the user exists. Note we
-        * secure exclusive lock to protect our update of the flat password
-        * file.
+        * Scan the pg_authid relation to be certain the user exists.
         */
-       pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
-       pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
+       pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
+       pg_authid_dsc = RelationGetDescr(pg_authid_rel);
 
-       tuple = SearchSysCache(SHADOWNAME,
-                                                  PointerGetDatum(stmt->user),
-                                                  0, 0, 0);
+       tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(stmt->role));
        if (!HeapTupleIsValid(tuple))
                ereport(ERROR,
                                (errcode(ERRCODE_UNDEFINED_OBJECT),
-                                errmsg("user \"%s\" does not exist", stmt->user)));
+                                errmsg("role \"%s\" does not exist", stmt->role)));
+
+       roleid = HeapTupleGetOid(tuple);
+
+       /*
+        * To mess with a superuser you gotta be superuser; else you need
+        * createrole, or just want to change your own password
+        */
+       if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper || issuper >= 0)
+       {
+               if (!superuser())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must be superuser to alter superusers")));
+       }
+       else if (((Form_pg_authid) GETSTRUCT(tuple))->rolreplication || isreplication >= 0)
+       {
+               if (!superuser())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must be superuser to alter replication users")));
+       }
+       else if (!have_createrole_privilege())
+       {
+               if (!(inherit < 0 &&
+                         createrole < 0 &&
+                         createdb < 0 &&
+                         canlogin < 0 &&
+                         isreplication < 0 &&
+                         !dconnlimit &&
+                         !rolemembers &&
+                         !validUntil &&
+                         dpassword &&
+                         roleid == GetUserId()))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("permission denied")));
+       }
+
+       /* Convert validuntil to internal form */
+       if (validUntil)
+       {
+               validUntil_datum = DirectFunctionCall3(timestamptz_in,
+                                                                                          CStringGetDatum(validUntil),
+                                                                                          ObjectIdGetDatum(InvalidOid),
+                                                                                          Int32GetDatum(-1));
+               validUntil_null = false;
+       }
+       else
+       {
+               /* fetch existing setting in case hook needs it */
+               validUntil_datum = SysCacheGetAttr(AUTHNAME, tuple,
+                                                                                  Anum_pg_authid_rolvaliduntil,
+                                                                                  &validUntil_null);
+       }
+
+       /*
+        * Call the password checking hook if there is one defined
+        */
+       if (check_password_hook && password)
+               (*check_password_hook) (stmt->role,
+                                                               password,
+                          isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+                                                               validUntil_datum,
+                                                               validUntil_null);
 
        /*
         * Build an updated tuple, perusing the information just obtained
         */
        MemSet(new_record, 0, sizeof(new_record));
-       MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
-       MemSet(new_record_repl, ' ', sizeof(new_record_repl));
+       MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+       MemSet(new_record_repl, false, sizeof(new_record_repl));
+
+       /*
+        * issuper/createrole/catupdate/etc
+        *
+        * XXX It's rather unclear how to handle catupdate.  It's probably best to
+        * keep it equal to the superuser status, otherwise you could end up with
+        * a situation where no existing superuser can alter the catalogs,
+        * including pg_authid!
+        */
+       if (issuper >= 0)
+       {
+               new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);
+               new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
+
+               new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);
+               new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true;
+       }
+
+       if (inherit >= 0)
+       {
+               new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
+               new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
+       }
 
-       new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
-                                                                                       CStringGetDatum(stmt->user));
-       new_record_repl[Anum_pg_shadow_usename - 1] = 'r';
+       if (createrole >= 0)
+       {
+               new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);
+               new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
+       }
 
-       /* 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_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0);
+               new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true;
        }
 
-       /*
-        * createuser (superuser) and catupd
-        *
-        * XXX It's rather unclear how to handle catupd.  It's probably best to
-        * keep it equal to the superuser status, otherwise you could end up
-        * with a situation where no existing superuser can alter the
-        * catalogs, including pg_shadow!
-        */
-       if (createuser >= 0)
+       if (canlogin >= 0)
        {
-               new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser > 0);
-               new_record_repl[Anum_pg_shadow_usesuper - 1] = 'r';
+               new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0);
+               new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true;
+       }
+
+       if (isreplication >= 0)
+       {
+               new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication > 0);
+               new_record_repl[Anum_pg_authid_rolreplication - 1] = true;
+       }
 
-               new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser > 0);
-               new_record_repl[Anum_pg_shadow_usecatupd - 1] = 'r';
+       if (dconnlimit)
+       {
+               new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
+               new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
        }
 
        /* password */
        if (password)
        {
                if (!encrypt_password || isMD5(password))
-                       new_record[Anum_pg_shadow_passwd - 1] =
-                               DirectFunctionCall1(textin, CStringGetDatum(password));
+                       new_record[Anum_pg_authid_rolpassword - 1] =
+                               CStringGetTextDatum(password);
                else
                {
-                       if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
-                                                       encrypted_password))
+                       if (!pg_md5_encrypt(password, stmt->role, strlen(stmt->role),
+                                                               encrypted_password))
                                elog(ERROR, "password encryption failed");
-                       new_record[Anum_pg_shadow_passwd - 1] =
-                               DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
+                       new_record[Anum_pg_authid_rolpassword - 1] =
+                               CStringGetTextDatum(encrypted_password);
                }
-               new_record_repl[Anum_pg_shadow_passwd - 1] = 'r';
+               new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
        }
 
-       /* valid until */
-       if (validUntil)
+       /* unset password */
+       if (dpassword && dpassword->arg == NULL)
        {
-               new_record[Anum_pg_shadow_valuntil - 1] =
-                       DirectFunctionCall1(abstimein, CStringGetDatum(validUntil));
-               new_record_repl[Anum_pg_shadow_valuntil - 1] = 'r';
+               new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
+               new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
        }
 
-       new_tuple = heap_modifytuple(tuple, pg_shadow_rel, new_record,
-                                                                new_record_nulls, new_record_repl);
-       simple_heap_update(pg_shadow_rel, &tuple->t_self, new_tuple);
+       /* valid until */
+       new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
+       new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
+       new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
+
+       new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
+                                                                 new_record_nulls, new_record_repl);
+       simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple);
 
        /* Update indexes */
-       CatalogUpdateIndexes(pg_shadow_rel, new_tuple);
+       CatalogUpdateIndexes(pg_authid_rel, new_tuple);
 
        ReleaseSysCache(tuple);
        heap_freetuple(new_tuple);
 
        /*
-        * Now we can clean up; but keep lock until commit (to avoid possible
-        * deadlock when commit code tries to acquire lock).
+        * Advance command counter so we can see new record; else tests in
+        * AddRoleMems may fail.
         */
-       heap_close(pg_shadow_rel, NoLock);
+       if (rolemembers)
+               CommandCounterIncrement();
+
+       if (stmt->action == +1)         /* add members to role */
+               AddRoleMems(stmt->role, roleid,
+                                       rolemembers, roleNamesToIds(rolemembers),
+                                       GetUserId(), false);
+       else if (stmt->action == -1)    /* drop members from role */
+               DelRoleMems(stmt->role, roleid,
+                                       rolemembers, roleNamesToIds(rolemembers),
+                                       false);
 
        /*
-        * Set flag to update flat password file at commit.
+        * Close pg_authid, but keep lock till commit.
         */
-       user_file_update_needed = true;
+       heap_close(pg_authid_rel, NoLock);
 }
 
 
 /*
- * ALTER USER ... SET
+ * ALTER ROLE ... SET
  */
 void
-AlterUserSet(AlterUserSetStmt *stmt)
+AlterRoleSet(AlterRoleSetStmt *stmt)
 {
-       char       *valuestr;
-       HeapTuple       oldtuple,
-                               newtuple;
-       Relation        rel;
-       Datum           repl_val[Natts_pg_shadow];
-       char            repl_null[Natts_pg_shadow];
-       char            repl_repl[Natts_pg_shadow];
-       int                     i;
+       HeapTuple       roletuple;
+       Oid                     databaseid = InvalidOid;
 
-       valuestr = flatten_set_variable_args(stmt->variable, stmt->value);
+       roletuple = SearchSysCache1(AUTHNAME, PointerGetDatum(stmt->role));
 
-       /*
-        * RowExclusiveLock is sufficient, because we don't need to update the
-        * flat password file.
-        */
-       rel = heap_openr(ShadowRelationName, RowExclusiveLock);
-       oldtuple = SearchSysCache(SHADOWNAME,
-                                                         PointerGetDatum(stmt->user),
-                                                         0, 0, 0);
-       if (!HeapTupleIsValid(oldtuple))
+       if (!HeapTupleIsValid(roletuple))
                ereport(ERROR,
                                (errcode(ERRCODE_UNDEFINED_OBJECT),
-                                errmsg("user \"%s\" does not exist", stmt->user)));
-
-       if (!(superuser()
-        || ((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetUserId()))
-               ereport(ERROR,
-                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("permission denied")));
+                                errmsg("role \"%s\" does not exist", stmt->role)));
 
-       for (i = 0; i < Natts_pg_shadow; i++)
-               repl_repl[i] = ' ';
+       /*
+        * Obtain a lock on the role and make sure it didn't go away in the
+        * meantime.
+        */
+       shdepLockAndCheckObject(AuthIdRelationId, HeapTupleGetOid(roletuple));
 
-       repl_repl[Anum_pg_shadow_useconfig - 1] = 'r';
-       if (strcmp(stmt->variable, "all") == 0 && valuestr == NULL)
+       /*
+        * To mess with a superuser you gotta be superuser; else you need
+        * createrole, or just want to change your own settings
+        */
+       if (((Form_pg_authid) GETSTRUCT(roletuple))->rolsuper)
        {
-               /* RESET ALL */
-               repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
+               if (!superuser())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must be superuser to alter superusers")));
        }
        else
        {
-               Datum           datum;
-               bool            isnull;
-               ArrayType  *array;
-
-               repl_null[Anum_pg_shadow_useconfig - 1] = ' ';
-
-               datum = SysCacheGetAttr(SHADOWNAME, oldtuple,
-                                                               Anum_pg_shadow_useconfig, &isnull);
-
-               array = isnull ? ((ArrayType *) NULL) : DatumGetArrayTypeP(datum);
-
-               if (valuestr)
-                       array = GUCArrayAdd(array, stmt->variable, valuestr);
-               else
-                       array = GUCArrayDelete(array, stmt->variable);
-
-               if (array)
-                       repl_val[Anum_pg_shadow_useconfig - 1] = PointerGetDatum(array);
-               else
-                       repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
+               if (!have_createrole_privilege() &&
+                       HeapTupleGetOid(roletuple) != GetUserId())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("permission denied")));
        }
 
-       newtuple = heap_modifytuple(oldtuple, rel, repl_val, repl_null, repl_repl);
-       simple_heap_update(rel, &oldtuple->t_self, newtuple);
-
-       CatalogUpdateIndexes(rel, newtuple);
+       /* look up and lock the database, if specified */
+       if (stmt->database != NULL)
+       {
+               databaseid = get_database_oid(stmt->database, false);
+               shdepLockAndCheckObject(DatabaseRelationId, databaseid);
+       }
 
-       ReleaseSysCache(oldtuple);
-       heap_close(rel, RowExclusiveLock);
+       AlterSetting(databaseid, HeapTupleGetOid(roletuple), stmt->setstmt);
+       ReleaseSysCache(roletuple);
 }
 
 
-
 /*
- * DROP USER
+ * DROP ROLE
  */
 void
-DropUser(DropUserStmt *stmt)
+DropRole(DropRoleStmt *stmt)
 {
-       Relation        pg_shadow_rel;
-       TupleDesc       pg_shadow_dsc;
-       List       *item;
+       Relation        pg_authid_rel,
+                               pg_auth_members_rel;
+       ListCell   *item;
 
-       if (!superuser())
+       if (!have_createrole_privilege())
                ereport(ERROR,
                                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("permission denied")));
+                                errmsg("permission denied to drop role")));
 
        /*
-        * Scan the pg_shadow relation to find the usesysid of the user to be
-        * deleted.  Note we secure exclusive lock, because we need to protect
-        * our update of the flat password file.
+        * Scan the pg_authid relation to find the Oid of the role(s) to be
+        * deleted.
         */
-       pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
-       pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
+       pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
+       pg_auth_members_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
 
-       foreach(item, stmt->users)
+       foreach(item, stmt->roles)
        {
-               const char *user = strVal(lfirst(item));
+               const char *role = strVal(lfirst(item));
                HeapTuple       tuple,
                                        tmp_tuple;
-               Relation        pg_rel;
-               TupleDesc       pg_dsc;
                ScanKeyData scankey;
-               HeapScanDesc scan;
-               AclId           usesysid;
+               char       *detail;
+               char       *detail_log;
+               SysScanDesc sscan;
+               Oid                     roleid;
 
-               tuple = SearchSysCache(SHADOWNAME,
-                                                          PointerGetDatum(user),
-                                                          0, 0, 0);
+               tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
                if (!HeapTupleIsValid(tuple))
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_UNDEFINED_OBJECT),
-                                        errmsg("user \"%s\" does not exist", user)));
+               {
+                       if (!stmt->missing_ok)
+                       {
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                                errmsg("role \"%s\" does not exist", role)));
+                       }
+                       else
+                       {
+                               ereport(NOTICE,
+                                               (errmsg("role \"%s\" does not exist, skipping",
+                                                               role)));
+                       }
 
-               usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->usesysid;
+                       continue;
+               }
+
+               roleid = HeapTupleGetOid(tuple);
 
-               if (usesysid == GetUserId())
+               if (roleid == GetUserId())
                        ereport(ERROR,
                                        (errcode(ERRCODE_OBJECT_IN_USE),
                                         errmsg("current user cannot be dropped")));
-               if (usesysid == GetSessionUserId())
+               if (roleid == GetOuterUserId())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_OBJECT_IN_USE),
+                                        errmsg("current user cannot be dropped")));
+               if (roleid == GetSessionUserId())
                        ereport(ERROR,
                                        (errcode(ERRCODE_OBJECT_IN_USE),
                                         errmsg("session user cannot be dropped")));
 
                /*
-                * Check if user still owns a database. If so, error out.
-                *
-                * (It used to be that this function would drop the database
-                * automatically. This is not only very dangerous for people that
-                * don't read the manual, it doesn't seem to be the behaviour one
-                * would expect either.) -- petere 2000/01/14)
+                * For safety's sake, we allow createrole holders to drop ordinary
+                * roles but not superuser roles.  This is mainly to avoid the
+                * scenario where you accidentally drop the last superuser.
                 */
-               pg_rel = heap_openr(DatabaseRelationName, AccessShareLock);
-               pg_dsc = RelationGetDescr(pg_rel);
-
-               ScanKeyEntryInitialize(&scankey, 0x0,
-                                                          Anum_pg_database_datdba, F_INT4EQ,
-                                                          Int32GetDatum(usesysid));
-
-               scan = heap_beginscan(pg_rel, SnapshotNow, 1, &scankey);
-
-               if ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
-               {
-                       char       *dbname;
-
-                       dbname = NameStr(((Form_pg_database) GETSTRUCT(tmp_tuple))->datname);
+               if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper &&
+                       !superuser())
                        ereport(ERROR,
-                                       (errcode(ERRCODE_OBJECT_IN_USE),
-                                        errmsg("user \"%s\" cannot be dropped", user),
-                                        errdetail("The user owns database \"%s\".", dbname)));
-               }
-
-               heap_endscan(scan);
-               heap_close(pg_rel, AccessShareLock);
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must be superuser to drop superusers")));
 
                /*
-                * Somehow we'd have to check for tables, views, etc. owned by the
-                * user as well, but those could be spread out over all sorts of
-                * databases which we don't have access to (easily).
+                * Lock the role, so nobody can add dependencies to her while we drop
+                * her.  We keep the lock until the end of transaction.
                 */
+               LockSharedObject(AuthIdRelationId, roleid, 0, AccessExclusiveLock);
+
+               /* Check for pg_shdepend entries depending on this role */
+               if (checkSharedDependencies(AuthIdRelationId, roleid,
+                                                                       &detail, &detail_log))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+                                        errmsg("role \"%s\" cannot be dropped because some objects depend on it",
+                                                       role),
+                                        errdetail("%s", detail),
+                                        errdetail_log("%s", detail_log)));
 
                /*
-                * Remove the user from the pg_shadow table
+                * Remove the role from the pg_authid table
                 */
-               simple_heap_delete(pg_shadow_rel, &tuple->t_self);
+               simple_heap_delete(pg_authid_rel, &tuple->t_self);
 
                ReleaseSysCache(tuple);
 
                /*
-                * Remove user from groups
+                * Remove role from the pg_auth_members table.  We have to remove all
+                * tuples that show it as either a role or a member.
                 *
-                * try calling alter group drop user for every group
+                * XXX what about grantor entries?      Maybe we should do one heap scan.
                 */
-               pg_rel = heap_openr(GroupRelationName, ExclusiveLock);
-               pg_dsc = RelationGetDescr(pg_rel);
-               scan = heap_beginscan(pg_rel, SnapshotNow, 0, NULL);
-               while ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+               ScanKeyInit(&scankey,
+                                       Anum_pg_auth_members_roleid,
+                                       BTEqualStrategyNumber, F_OIDEQ,
+                                       ObjectIdGetDatum(roleid));
+
+               sscan = systable_beginscan(pg_auth_members_rel, AuthMemRoleMemIndexId,
+                                                                  true, SnapshotNow, 1, &scankey);
+
+               while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
                {
-                       AlterGroupStmt ags;
+                       simple_heap_delete(pg_auth_members_rel, &tmp_tuple->t_self);
+               }
+
+               systable_endscan(sscan);
+
+               ScanKeyInit(&scankey,
+                                       Anum_pg_auth_members_member,
+                                       BTEqualStrategyNumber, F_OIDEQ,
+                                       ObjectIdGetDatum(roleid));
+
+               sscan = systable_beginscan(pg_auth_members_rel, AuthMemMemRoleIndexId,
+                                                                  true, SnapshotNow, 1, &scankey);
 
-                       /* the group name from which to try to drop the user: */
-                       ags.name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tmp_tuple))->groname));
-                       ags.action = -1;
-                       ags.listUsers = makeList1(makeInteger(usesysid));
-                       AlterGroup(&ags, "DROP USER");
+               while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+               {
+                       simple_heap_delete(pg_auth_members_rel, &tmp_tuple->t_self);
                }
-               heap_endscan(scan);
-               heap_close(pg_rel, ExclusiveLock);
+
+               systable_endscan(sscan);
 
                /*
-                * Advance command counter so that later iterations of this loop
-                * will see the changes already made.  This is essential if, for
-                * example, we are trying to drop two users who are members of the
-                * same group --- the AlterGroup for the second user had better
-                * see the tuple updated from the first one.
+                * Remove any comments on this role.
+                */
+               DeleteSharedComments(roleid, AuthIdRelationId);
+
+               /*
+                * Remove settings for this role.
+                */
+               DropSetting(InvalidOid, roleid);
+
+               /*
+                * Advance command counter so that later iterations of this loop will
+                * see the changes already made.  This is essential if, for example,
+                * we are trying to drop both a role and one of its direct members ---
+                * we'll get an error if we try to delete the linking pg_auth_members
+                * tuple twice.  (We do not need a CCI between the two delete loops
+                * above, because it's not allowed for a role to directly contain
+                * itself.)
                 */
                CommandCounterIncrement();
        }
 
        /*
-        * Now we can clean up; but keep lock until commit (to avoid possible
-        * deadlock when commit code tries to acquire lock).
-        */
-       heap_close(pg_shadow_rel, NoLock);
-
-       /*
-        * Set flag to update flat password file at commit.
+        * Now we can clean up; but keep locks until commit.
         */
-       user_file_update_needed = true;
+       heap_close(pg_auth_members_rel, NoLock);
+       heap_close(pg_authid_rel, NoLock);
 }
 
-
 /*
- * Rename user
+ * Rename role
  */
 void
-RenameUser(const char *oldname, const char *newname)
+RenameRole(const char *oldname, const char *newname)
 {
-       HeapTuple       tup;
+       HeapTuple       oldtuple,
+                               newtuple;
+       TupleDesc       dsc;
        Relation        rel;
+       Datum           datum;
+       bool            isnull;
+       Datum           repl_val[Natts_pg_authid];
+       bool            repl_null[Natts_pg_authid];
+       bool            repl_repl[Natts_pg_authid];
+       int                     i;
+       Oid                     roleid;
 
-       /* ExclusiveLock because we need to update the password file */
-       rel = heap_openr(ShadowRelationName, ExclusiveLock);
+       rel = heap_open(AuthIdRelationId, RowExclusiveLock);
+       dsc = RelationGetDescr(rel);
 
-       tup = SearchSysCacheCopy(SHADOWNAME,
-                                                        CStringGetDatum(oldname),
-                                                        0, 0, 0);
-       if (!HeapTupleIsValid(tup))
+       oldtuple = SearchSysCache1(AUTHNAME, CStringGetDatum(oldname));
+       if (!HeapTupleIsValid(oldtuple))
                ereport(ERROR,
                                (errcode(ERRCODE_UNDEFINED_OBJECT),
-                                errmsg("user \"%s\" does not exist", oldname)));
+                                errmsg("role \"%s\" does not exist", 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.
+        * 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.      Same for SET ROLE ... we don't restrict renaming the current
+        * effective userid, though.
         */
-       if (((Form_pg_shadow) GETSTRUCT(tup))->usesysid == GetSessionUserId())
+
+       roleid = HeapTupleGetOid(oldtuple);
+
+       if (roleid == GetSessionUserId())
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                errmsg("session user may not be renamed")));
+                                errmsg("session user cannot be renamed")));
+       if (roleid == GetOuterUserId())
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("current user cannot be renamed")));
 
        /* make sure the new name doesn't exist */
-       if (SearchSysCacheExists(SHADOWNAME,
-                                                        CStringGetDatum(newname),
-                                                        0, 0, 0))
+       if (SearchSysCacheExists1(AUTHNAME, CStringGetDatum(newname)))
                ereport(ERROR,
                                (errcode(ERRCODE_DUPLICATE_OBJECT),
-                                errmsg("user \"%s\" already exists", newname)));
+                                errmsg("role \"%s\" already exists", newname)));
 
-       /* must be superuser */
-       if (!superuser())
+       if (strcmp(newname, "public") == 0 ||
+               strcmp(newname, "none") == 0)
                ereport(ERROR,
-                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("permission denied")));
+                               (errcode(ERRCODE_RESERVED_NAME),
+                                errmsg("role name \"%s\" is reserved",
+                                               newname)));
 
-       /* rename */
-       namestrcpy(&(((Form_pg_shadow) GETSTRUCT(tup))->usename), newname);
-       simple_heap_update(rel, &tup->t_self, tup);
-       CatalogUpdateIndexes(rel, tup);
+       /*
+        * createrole is enough privilege unless you want to mess with a superuser
+        */
+       if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
+       {
+               if (!superuser())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must be superuser to rename superusers")));
+       }
+       else
+       {
+               if (!have_createrole_privilege())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("permission denied to rename role")));
+       }
 
-       heap_close(rel, NoLock);
-       heap_freetuple(tup);
+       /* OK, construct the modified tuple */
+       for (i = 0; i < Natts_pg_authid; i++)
+               repl_repl[i] = false;
 
-       user_file_update_needed = true;
-}
+       repl_repl[Anum_pg_authid_rolname - 1] = true;
+       repl_val[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein,
+                                                                                                  CStringGetDatum(newname));
+       repl_null[Anum_pg_authid_rolname - 1] = false;
 
+       datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
 
-/*
- * CheckPgUserAclNotNull
- *
- * check to see if there is an ACL on pg_shadow
- */
-static void
-CheckPgUserAclNotNull(void)
-{
-       HeapTuple       htup;
+       if (!isnull && isMD5(TextDatumGetCString(datum)))
+       {
+               /* MD5 uses the username as salt, so just clear it on a rename */
+               repl_repl[Anum_pg_authid_rolpassword - 1] = true;
+               repl_null[Anum_pg_authid_rolpassword - 1] = true;
 
-       htup = SearchSysCache(RELOID,
-                                                 ObjectIdGetDatum(RelOid_pg_shadow),
-                                                 0, 0, 0);
-       if (!HeapTupleIsValid(htup)) /* should not happen, we hope */
-               elog(ERROR, "cache lookup failed for relation %u", RelOid_pg_shadow);
+               ereport(NOTICE,
+                               (errmsg("MD5 password cleared because of role rename")));
+       }
 
-       if (heap_attisnull(htup, Anum_pg_class_relacl))
-               ereport(ERROR,
-                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("before using passwords you must revoke permissions on %s",
-                                               ShadowRelationName),
-                                errdetail("This restriction is to prevent unprivileged users from reading the passwords."),
-                                errhint("Try 'REVOKE ALL ON \"%s\" FROM PUBLIC'.",
-                                                ShadowRelationName)));
+       newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
+       simple_heap_update(rel, &oldtuple->t_self, newtuple);
 
-       ReleaseSysCache(htup);
-}
+       CatalogUpdateIndexes(rel, newtuple);
 
+       ReleaseSysCache(oldtuple);
 
+       /*
+        * Close pg_authid, but keep lock till commit.
+        */
+       heap_close(rel, NoLock);
+}
 
 /*
- * CREATE GROUP
+ * GrantRoleStmt
+ *
+ * Grant/Revoke roles to/from roles
  */
 void
-CreateGroup(CreateGroupStmt *stmt)
+GrantRole(GrantRoleStmt *stmt)
 {
-       Relation        pg_group_rel;
-       HeapScanDesc scan;
-       HeapTuple       tuple;
-       TupleDesc       pg_group_dsc;
-       bool            group_exists = false,
-                               sysid_exists = false,
-                               havesysid = false;
-       int                     max_id;
-       Datum           new_record[Natts_pg_group];
-       char            new_record_nulls[Natts_pg_group];
-       List       *item,
-                          *option,
-                          *newlist = NIL;
-       IdList     *grolist;
-       int                     sysid = 0;
-       List       *userElts = NIL;
-       DefElem    *dsysid = NULL;
-       DefElem    *duserElts = NULL;
+       Relation        pg_authid_rel;
+       Oid                     grantor;
+       List       *grantee_ids;
+       ListCell   *item;
 
-       foreach(option, stmt->options)
-       {
-               DefElem    *defel = (DefElem *) lfirst(option);
-
-               if (strcmp(defel->defname, "sysid") == 0)
-               {
-                       if (dsysid)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                errmsg("conflicting or redundant options")));
-                       dsysid = defel;
-               }
-               else if (strcmp(defel->defname, "userElts") == 0)
-               {
-                       if (duserElts)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                errmsg("conflicting or redundant options")));
-                       duserElts = defel;
-               }
-               else
-                       elog(ERROR, "option \"%s\" not recognized",
-                                defel->defname);
-       }
+       if (stmt->grantor)
+               grantor = get_role_oid(stmt->grantor, false);
+       else
+               grantor = GetUserId();
 
-       if (dsysid)
-       {
-               sysid = intVal(dsysid->arg);
-               if (sysid <= 0)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                        errmsg("group id must be positive")));
-               havesysid = true;
-       }
+       grantee_ids = roleNamesToIds(stmt->grantee_roles);
 
-       if (duserElts)
-               userElts = (List *) duserElts->arg;
+       /* AccessShareLock is enough since we aren't modifying pg_authid */
+       pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
 
        /*
-        * Make sure the user can do this.
+        * Step through all of the granted roles and add/remove entries for the
+        * grantees, or, if admin_opt is set, then just add/remove the admin
+        * option.
+        *
+        * Note: Permissions checking is done by AddRoleMems/DelRoleMems
         */
-       if (!superuser())
-               ereport(ERROR,
-                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("permission denied")));
+       foreach(item, stmt->granted_roles)
+       {
+               AccessPriv *priv = (AccessPriv *) lfirst(item);
+               char       *rolename = priv->priv_name;
+               Oid                     roleid;
 
-       if (strcmp(stmt->name, "public") == 0)
-               ereport(ERROR,
-                               (errcode(ERRCODE_RESERVED_NAME),
-                                errmsg("group name \"%s\" is reserved",
-                                               stmt->name)));
+               /* Must reject priv(columns) and ALL PRIVILEGES(columns) */
+               if (rolename == NULL || priv->cols != NIL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+                       errmsg("column names cannot be included in GRANT/REVOKE ROLE")));
+
+               roleid = get_role_oid(rolename, false);
+               if (stmt->is_grant)
+                       AddRoleMems(rolename, roleid,
+                                               stmt->grantee_roles, grantee_ids,
+                                               grantor, stmt->admin_opt);
+               else
+                       DelRoleMems(rolename, roleid,
+                                               stmt->grantee_roles, grantee_ids,
+                                               stmt->admin_opt);
+       }
 
        /*
-        * Scan the pg_group relation to be certain the group or id doesn't
-        * already exist.  Note we secure exclusive lock, because we also need
-        * to be sure of what the next grosysid should be, and we need to
-        * protect our eventual update of the flat group file.
+        * Close pg_authid, but keep lock till commit.
         */
-       pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
-       pg_group_dsc = RelationGetDescr(pg_group_rel);
+       heap_close(pg_authid_rel, NoLock);
+}
 
-       scan = heap_beginscan(pg_group_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);
-               int32           this_sysid;
+/*
+ * DropOwnedObjects
+ *
+ * Drop the objects owned by a given list of roles.
+ */
+void
+DropOwnedObjects(DropOwnedStmt *stmt)
+{
+       List       *role_ids = roleNamesToIds(stmt->roles);
+       ListCell   *cell;
 
-               group_exists = (strcmp(NameStr(group_form->groname), stmt->name) == 0);
+       /* Check privileges */
+       foreach(cell, role_ids)
+       {
+               Oid                     roleid = lfirst_oid(cell);
 
-               this_sysid = group_form->grosysid;
-               if (havesysid)                  /* customized id wanted */
-                       sysid_exists = (this_sysid == sysid);
-               else
-               {
-                       /* pick 1 + max */
-                       if (this_sysid > max_id)
-                               max_id = this_sysid;
-               }
+               if (!has_privs_of_role(GetUserId(), roleid))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("permission denied to drop objects")));
        }
-       heap_endscan(scan);
 
-       if (group_exists)
-               ereport(ERROR,
-                               (errcode(ERRCODE_DUPLICATE_OBJECT),
-                                errmsg("group \"%s\" already exists",
-                                               stmt->name)));
-       if (sysid_exists)
-               ereport(ERROR,
-                               (errcode(ERRCODE_DUPLICATE_OBJECT),
-                                errmsg("sysid %d is already assigned", sysid)));
+       /* Ok, do it */
+       shdepDropOwned(role_ids, stmt->behavior);
+}
 
-       /* If no sysid given, use max existing id + 1 */
-       if (!havesysid)
-               sysid = max_id + 1;
+/*
+ * ReassignOwnedObjects
+ *
+ * Give the objects owned by a given list of roles away to another user.
+ */
+void
+ReassignOwnedObjects(ReassignOwnedStmt *stmt)
+{
+       List       *role_ids = roleNamesToIds(stmt->roles);
+       ListCell   *cell;
+       Oid                     newrole;
 
-       /*
-        * Translate the given user names to ids
-        */
-       foreach(item, userElts)
+       /* Check privileges */
+       foreach(cell, role_ids)
        {
-               const char *groupuser = strVal(lfirst(item));
-               int32           userid = get_usesysid(groupuser);
+               Oid                     roleid = lfirst_oid(cell);
 
-               if (!intMember(userid, newlist))
-                       newlist = lappendi(newlist, userid);
+               if (!has_privs_of_role(GetUserId(), roleid))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("permission denied to reassign objects")));
        }
 
-       /* build an array to insert */
-       if (newlist)
-               grolist = IdListToArray(newlist);
-       else
-               grolist = NULL;
+       /* Must have privileges on the receiving side too */
+       newrole = get_role_oid(stmt->newrole, false);
 
-       /*
-        * Form a tuple to insert
-        */
-       new_record[Anum_pg_group_groname - 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_nulls[Anum_pg_group_groname - 1] = ' ';
-       new_record_nulls[Anum_pg_group_grosysid - 1] = ' ';
-       new_record_nulls[Anum_pg_group_grolist - 1] = grolist ? ' ' : 'n';
-
-       tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
+       if (!has_privs_of_role(GetUserId(), newrole))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                errmsg("permission denied to reassign objects")));
 
-       /*
-        * Insert a new record in the pg_group table
-        */
-       simple_heap_insert(pg_group_rel, tuple);
+       /* Ok, do it */
+       shdepReassignOwned(role_ids, newrole);
+}
 
-       /* Update indexes */
-       CatalogUpdateIndexes(pg_group_rel, tuple);
+/*
+ * roleNamesToIds
+ *
+ * Given a list of role names (as String nodes), generate a list of role OIDs
+ * in the same order.
+ */
+static List *
+roleNamesToIds(List *memberNames)
+{
+       List       *result = NIL;
+       ListCell   *l;
 
-       /*
-        * 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);
+       foreach(l, memberNames)
+       {
+               char       *rolename = strVal(lfirst(l));
+               Oid                     roleid = get_role_oid(rolename, false);
 
-       /*
-        * Set flag to update flat group file at commit.
-        */
-       group_file_update_needed = true;
+               result = lappend_oid(result, roleid);
+       }
+       return result;
 }
 
-
 /*
- * ALTER GROUP
+ * AddRoleMems -- Add given members to the specified role
+ *
+ * rolename: name of role to add to (used only for error messages)
+ * roleid: OID of role to add to
+ * memberNames: list of names of roles to add (used only for error messages)
+ * memberIds: OIDs of roles to add
+ * grantorId: who is granting the membership
+ * admin_opt: granting admin option?
+ *
+ * Note: caller is responsible for calling auth_file_update_needed().
  */
-void
-AlterGroup(AlterGroupStmt *stmt, const char *tag)
+static void
+AddRoleMems(const char *rolename, Oid roleid,
+                       List *memberNames, List *memberIds,
+                       Oid grantorId, bool admin_opt)
 {
-       Relation        pg_group_rel;
-       TupleDesc       pg_group_dsc;
-       HeapTuple       group_tuple;
-       IdList     *oldarray;
-       Datum           datum;
-       bool            null;
-       List       *newlist,
-                          *item;
+       Relation        pg_authmem_rel;
+       TupleDesc       pg_authmem_dsc;
+       ListCell   *nameitem;
+       ListCell   *iditem;
 
-       /*
-        * Make sure the user can do this.
-        */
-       if (!superuser())
-               ereport(ERROR,
-                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("permission denied")));
+       Assert(list_length(memberNames) == list_length(memberIds));
 
-       /*
-        * 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);
+       /* Skip permission check if nothing to do */
+       if (!memberIds)
+               return;
 
        /*
-        * Fetch existing tuple for group.
+        * Check permissions: must have createrole or admin option on the role to
+        * be changed.  To mess with a superuser role, you gotta be superuser.
         */
-       group_tuple = SearchSysCache(GRONAME,
-                                                                PointerGetDatum(stmt->name),
-                                                                0, 0, 0);
-       if (!HeapTupleIsValid(group_tuple))
+       if (superuser_arg(roleid))
+       {
+               if (!superuser())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must be superuser to alter superusers")));
+       }
+       else
+       {
+               if (!have_createrole_privilege() &&
+                       !is_admin_of_role(grantorId, roleid))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must have admin option on role \"%s\"",
+                                                       rolename)));
+       }
+
+       /* XXX not sure about this check */
+       if (grantorId != GetUserId() && !superuser())
                ereport(ERROR,
-                               (errcode(ERRCODE_UNDEFINED_OBJECT),
-                                errmsg("group \"%s\" does not exist", stmt->name)));
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                errmsg("must be superuser to set grantor")));
 
-       /* Fetch old group membership. */
-       datum = heap_getattr(group_tuple, Anum_pg_group_grolist,
-                                                pg_group_dsc, &null);
-       oldarray = null ? ((IdList *) NULL) : DatumGetIdListP(datum);
+       pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
+       pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
 
-       /* initialize list with old array contents */
-       newlist = IdArrayToList(oldarray);
+       forboth(nameitem, memberNames, iditem, memberIds)
+       {
+               const char *membername = strVal(lfirst(nameitem));
+               Oid                     memberid = lfirst_oid(iditem);
+               HeapTuple       authmem_tuple;
+               HeapTuple       tuple;
+               Datum           new_record[Natts_pg_auth_members];
+               bool            new_record_nulls[Natts_pg_auth_members];
+               bool            new_record_repl[Natts_pg_auth_members];
 
-       /*
-        * Now decide what to do.
-        */
-       AssertState(stmt->action == +1 || stmt->action == -1);
+               /*
+                * Refuse creation of membership loops, including the trivial case
+                * where a role is made a member of itself.  We do this by checking to
+                * see if the target role is already a member of the proposed member
+                * role.  We have to ignore possible superuserness, however, else we
+                * could never grant membership in a superuser-privileged role.
+                */
+               if (is_member_of_role_nosuper(roleid, memberid))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+                                        (errmsg("role \"%s\" is a member of role \"%s\"",
+                                                        rolename, membername))));
 
-       if (stmt->action == +1)         /* add users, might also be invoked by
-                                                                * create user */
-       {
                /*
-                * convert the to be added usernames to sysids and add them to the
-                * list
+                * Check if entry for this role/member already exists; if so, give
+                * warning unless we are adding admin option.
                 */
-               foreach(item, stmt->listUsers)
+               authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM,
+                                                                               ObjectIdGetDatum(roleid),
+                                                                               ObjectIdGetDatum(memberid));
+               if (HeapTupleIsValid(authmem_tuple) &&
+                       (!admin_opt ||
+                        ((Form_pg_auth_members) GETSTRUCT(authmem_tuple))->admin_option))
                {
-                       int32           sysid;
-
-                       if (strcmp(tag, "ALTER GROUP") == 0)
-                       {
-                               /* Get the uid of the proposed user to add. */
-                               sysid = get_usesysid(strVal(lfirst(item)));
-                       }
-                       else if (strcmp(tag, "CREATE USER") == 0)
-                       {
-                               /*
-                                * in this case we already know the uid and it wouldn't be
-                                * in the cache anyway yet
-                                */
-                               sysid = intVal(lfirst(item));
-                       }
-                       else
-                       {
-                               elog(ERROR, "unexpected tag: \"%s\"", tag);
-                               sysid = 0;              /* keep compiler quiet */
-                       }
-
-                       if (!intMember(sysid, newlist))
-                               newlist = lappendi(newlist, sysid);
-                       else
-                               ereport(WARNING,
-                                               (errcode(ERRCODE_DUPLICATE_OBJECT),
-                                                errmsg("user \"%s\" is already in group \"%s\"",
-                                                               strVal(lfirst(item)), stmt->name)));
+                       ereport(NOTICE,
+                                       (errmsg("role \"%s\" is already a member of role \"%s\"",
+                                                       membername, rolename)));
+                       ReleaseSysCache(authmem_tuple);
+                       continue;
                }
 
-               /* Do the update */
-               UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
-       }                                                       /* endif alter group add user */
+               /* Build a tuple to insert or update */
+               MemSet(new_record, 0, sizeof(new_record));
+               MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+               MemSet(new_record_repl, false, sizeof(new_record_repl));
 
-       else if (stmt->action == -1)    /* drop users from group */
-       {
-               bool            is_dropuser = strcmp(tag, "DROP USER") == 0;
+               new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);
+               new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid);
+               new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId);
+               new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
 
-               if (newlist == NIL)
+               if (HeapTupleIsValid(authmem_tuple))
                {
-                       if (!is_dropuser)
-                               ereport(WARNING,
-                                               (errcode(ERRCODE_WARNING),
-                                                errmsg("group \"%s\" does not have any members",
-                                                               stmt->name)));
+                       new_record_repl[Anum_pg_auth_members_grantor - 1] = true;
+                       new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+                       tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
+                                                                         new_record,
+                                                                         new_record_nulls, new_record_repl);
+                       simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple);
+                       CatalogUpdateIndexes(pg_authmem_rel, tuple);
+                       ReleaseSysCache(authmem_tuple);
                }
                else
                {
-                       /*
-                        * convert the to be dropped usernames to sysids and remove
-                        * them from the list
-                        */
-                       foreach(item, stmt->listUsers)
-                       {
-                               int32           sysid;
-
-                               if (!is_dropuser)
-                               {
-                                       /* Get the uid of the proposed user to drop. */
-                                       sysid = get_usesysid(strVal(lfirst(item)));
-                               }
-                               else
-                               {
-                                       /* for dropuser we already know the uid */
-                                       sysid = intVal(lfirst(item));
-                               }
-                               if (intMember(sysid, newlist))
-                                       newlist = lremovei(sysid, newlist);
-                               else if (!is_dropuser)
-                                       ereport(WARNING,
-                                                       (errcode(ERRCODE_WARNING),
-                                                        errmsg("user \"%s\" is not in group \"%s\"",
-                                                                       strVal(lfirst(item)), stmt->name)));
-                       }
-
-                       /* Do the update */
-                       UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
-               }                                               /* endif group not null */
-       }                                                       /* endif alter group drop user */
-
-       ReleaseSysCache(group_tuple);
+                       tuple = heap_form_tuple(pg_authmem_dsc,
+                                                                       new_record, new_record_nulls);
+                       simple_heap_insert(pg_authmem_rel, tuple);
+                       CatalogUpdateIndexes(pg_authmem_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);
+               /* CCI after each change, in case there are duplicates in list */
+               CommandCounterIncrement();
+       }
 
        /*
-        * Set flag to update flat group file at commit.
+        * Close pg_authmem, but keep lock till commit.
         */
-       group_file_update_needed = true;
+       heap_close(pg_authmem_rel, NoLock);
 }
 
 /*
- * Subroutine for AlterGroup: given a pg_group tuple and a desired new
- * membership (expressed as an integer list), form and write an updated tuple.
- * The pg_group relation must be open and locked already.
+ * DelRoleMems -- Remove given members from the specified role
+ *
+ * rolename: name of role to del from (used only for error messages)
+ * roleid: OID of role to del from
+ * memberNames: list of names of roles to del (used only for error messages)
+ * memberIds: OIDs of roles to del
+ * admin_opt: remove admin option only?
+ *
+ * Note: caller is responsible for calling auth_file_update_needed().
  */
 static void
-UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
-                                         List *members)
+DelRoleMems(const char *rolename, Oid roleid,
+                       List *memberNames, List *memberIds,
+                       bool admin_opt)
 {
-       IdList     *newarray;
-       Datum           new_record[Natts_pg_group];
-       char            new_record_nulls[Natts_pg_group];
-       char            new_record_repl[Natts_pg_group];
-       HeapTuple       tuple;
+       Relation        pg_authmem_rel;
+       TupleDesc       pg_authmem_dsc;
+       ListCell   *nameitem;
+       ListCell   *iditem;
 
-       newarray = IdListToArray(members);
+       Assert(list_length(memberNames) == list_length(memberIds));
+
+       /* Skip permission check if nothing to do */
+       if (!memberIds)
+               return;
 
        /*
-        * Form an updated tuple with the new array and write it back.
+        * Check permissions: must have createrole or admin option on the role to
+        * be changed.  To mess with a superuser role, you gotta be superuser.
         */
-       MemSet(new_record, 0, sizeof(new_record));
-       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';
-
-       tuple = heap_modifytuple(group_tuple, group_rel,
-                                                 new_record, new_record_nulls, new_record_repl);
-
-       simple_heap_update(group_rel, &group_tuple->t_self, tuple);
-
-       /* Update indexes */
-       CatalogUpdateIndexes(group_rel, tuple);
-}
-
-
-/*
- * Convert an integer list of sysids to an array.
- */
-static IdList *
-IdListToArray(List *members)
-{
-       int                     nmembers = length(members);
-       IdList     *newarray;
-       List       *item;
-       int                     i;
-
-       newarray = palloc(ARR_OVERHEAD(1) + nmembers * sizeof(int32));
-       newarray->size = ARR_OVERHEAD(1) + nmembers * sizeof(int32);
-       newarray->flags = 0;
-       newarray->elemtype = INT4OID;
-       ARR_NDIM(newarray) = 1;         /* one dimensional array */
-       ARR_LBOUND(newarray)[0] = 1;    /* axis starts at one */
-       ARR_DIMS(newarray)[0] = nmembers;       /* axis is this long */
-       i = 0;
-       foreach(item, members)
-               ((int *) ARR_DATA_PTR(newarray))[i++] = lfirsti(item);
-
-       return newarray;
-}
-
-/*
- * Convert an array of sysids to an integer list.
- */
-static List *
-IdArrayToList(IdList *oldarray)
-{
-       List       *newlist = NIL;
-       int                     hibound,
-                               i;
-
-       if (oldarray == NULL)
-               return NIL;
-
-       Assert(ARR_NDIM(oldarray) == 1);
-       Assert(ARR_ELEMTYPE(oldarray) == INT4OID);
-
-       hibound = ARR_DIMS(oldarray)[0];
-
-       for (i = 0; i < hibound; i++)
+       if (superuser_arg(roleid))
        {
-               int32           sysid;
-
-               sysid = ((int32 *) ARR_DATA_PTR(oldarray))[i];
-               /* filter out any duplicates --- probably a waste of time */
-               if (!intMember(sysid, newlist))
-                       newlist = lappendi(newlist, sysid);
+               if (!superuser())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must be superuser to alter superusers")));
+       }
+       else
+       {
+               if (!have_createrole_privilege() &&
+                       !is_admin_of_role(GetUserId(), roleid))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must have admin option on role \"%s\"",
+                                                       rolename)));
        }
 
-       return newlist;
-}
-
-
-/*
- * DROP GROUP
- */
-void
-DropGroup(DropGroupStmt *stmt)
-{
-       Relation        pg_group_rel;
-       HeapTuple       tuple;
-
-       /*
-        * Make sure the user can do this.
-        */
-       if (!superuser())
-               ereport(ERROR,
-                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("permission denied")));
+       pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
+       pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
 
-       /*
-        * Secure exclusive lock to protect our update of the flat group file.
-        */
-       pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
+       forboth(nameitem, memberNames, iditem, memberIds)
+       {
+               const char *membername = strVal(lfirst(nameitem));
+               Oid                     memberid = lfirst_oid(iditem);
+               HeapTuple       authmem_tuple;
 
-       /* Find and delete the group. */
+               /*
+                * Find entry for this role/member
+                */
+               authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM,
+                                                                               ObjectIdGetDatum(roleid),
+                                                                               ObjectIdGetDatum(memberid));
+               if (!HeapTupleIsValid(authmem_tuple))
+               {
+                       ereport(WARNING,
+                                       (errmsg("role \"%s\" is not a member of role \"%s\"",
+                                                       membername, rolename)));
+                       continue;
+               }
 
-       tuple = SearchSysCacheCopy(GRONAME,
-                                                          PointerGetDatum(stmt->name),
-                                                          0, 0, 0);
-       if (!HeapTupleIsValid(tuple))
-               ereport(ERROR,
-                               (errcode(ERRCODE_UNDEFINED_OBJECT),
-                                errmsg("group \"%s\" does not exist", stmt->name)));
+               if (!admin_opt)
+               {
+                       /* Remove the entry altogether */
+                       simple_heap_delete(pg_authmem_rel, &authmem_tuple->t_self);
+               }
+               else
+               {
+                       /* Just turn off the admin option */
+                       HeapTuple       tuple;
+                       Datum           new_record[Natts_pg_auth_members];
+                       bool            new_record_nulls[Natts_pg_auth_members];
+                       bool            new_record_repl[Natts_pg_auth_members];
+
+                       /* Build a tuple to update with */
+                       MemSet(new_record, 0, sizeof(new_record));
+                       MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+                       MemSet(new_record_repl, false, sizeof(new_record_repl));
+
+                       new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false);
+                       new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+
+                       tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
+                                                                         new_record,
+                                                                         new_record_nulls, new_record_repl);
+                       simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple);
+                       CatalogUpdateIndexes(pg_authmem_rel, tuple);
+               }
 
-       simple_heap_delete(pg_group_rel, &tuple->t_self);
+               ReleaseSysCache(authmem_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);
+               /* CCI after each change, in case there are duplicates in list */
+               CommandCounterIncrement();
+       }
 
        /*
-        * Set flag to update flat group file at commit.
+        * Close pg_authmem, but keep lock till commit.
         */
-       group_file_update_needed = true;
-}
-
-
-/*
- * Rename group
- */
-void
-RenameGroup(const char *oldname, const char *newname)
-{
-       HeapTuple       tup;
-       Relation        rel;
-
-       /* ExclusiveLock because we need to update the flat group file */
-       rel = heap_openr(GroupRelationName, ExclusiveLock);
-
-       tup = SearchSysCacheCopy(GRONAME,
-                                                        CStringGetDatum(oldname),
-                                                        0, 0, 0);
-       if (!HeapTupleIsValid(tup))
-               ereport(ERROR,
-                               (errcode(ERRCODE_UNDEFINED_OBJECT),
-                                errmsg("group \"%s\" does not exist", oldname)));
-
-       /* make sure the new name doesn't exist */
-       if (SearchSysCacheExists(GRONAME,
-                                                        CStringGetDatum(newname),
-                                                        0, 0, 0))
-               ereport(ERROR,
-                               (errcode(ERRCODE_DUPLICATE_OBJECT),
-                                errmsg("group \"%s\" already exists", newname)));
-
-       /* must be superuser */
-       if (!superuser())
-               ereport(ERROR,
-                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("permission denied")));
-
-       /* rename */
-       namestrcpy(&(((Form_pg_group) GETSTRUCT(tup))->groname), newname);
-       simple_heap_update(rel, &tup->t_self, tup);
-       CatalogUpdateIndexes(rel, tup);
-
-       heap_close(rel, NoLock);
-       heap_freetuple(tup);
-
-       group_file_update_needed = true;
+       heap_close(pg_authmem_rel, NoLock);
 }