]> granicus.if.org Git - postgresql/blobdiff - src/backend/utils/adt/acl.c
Update copyright for the year 2010.
[postgresql] / src / backend / utils / adt / acl.c
index 494a262f0bdbea68d61bc129dfde6707d1794221..f705d0eac10db5d521b51920ae12c06f6e88efe6 100644 (file)
@@ -3,12 +3,12 @@
  * acl.c
  *       Basic access control list data structures manipulation routines.
  *
- * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/utils/adt/acl.c,v 1.75 2002/08/09 16:45:14 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.154 2010/01/02 16:57:53 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include <ctype.h>
 
 #include "catalog/namespace.h"
-#include "catalog/pg_shadow.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_members.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_class.h"
 #include "commands/dbcommands.h"
+#include "commands/tablespace.h"
+#include "foreign/foreign.h"
+#include "funcapi.h"
 #include "miscadmin.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/syscache.h"
 
 
-#define ACL_IDTYPE_GID_KEYWORD "group"
-#define ACL_IDTYPE_UID_KEYWORD "user"
+typedef struct
+{
+       const char *name;
+       AclMode         value;
+} priv_map;
 
-static const char *getid(const char *s, char *n);
-static Acl *makeacl(int n);
-static const char *aclparse(const char *s, AclItem *aip, unsigned *modechg);
-static bool aclitemeq(const AclItem *a1, const AclItem *a2);
-static bool aclitemgt(const AclItem *a1, const AclItem *a2);
+/*
+ * We frequently need to test whether a given role is a member of some other
+ * role.  In most of these tests the "given role" is the same, namely the
+ * active current user.  So we can optimize it by keeping a cached list of
+ * all the roles the "given role" is a member of, directly or indirectly.
+ * The cache is flushed whenever we detect a change in pg_auth_members.
+ *
+ * There are actually two caches, one computed under "has_privs" rules
+ * (do not recurse where rolinherit isn't true) and one computed under
+ * "is_member" rules (recurse regardless of rolinherit).
+ *
+ * Possibly this mechanism should be generalized to allow caching membership
+ * info for multiple roles?
+ *
+ * The has_privs cache is:
+ * cached_privs_role is the role OID the cache is for.
+ * cached_privs_roles is an OID list of roles that cached_privs_role
+ *             has the privileges of (always including itself).
+ * The cache is valid if cached_privs_role is not InvalidOid.
+ *
+ * The is_member cache is similarly:
+ * cached_member_role is the role OID the cache is for.
+ * cached_membership_roles is an OID list of roles that cached_member_role
+ *             is a member of (always including itself).
+ * The cache is valid if cached_member_role is not InvalidOid.
+ */
+static Oid     cached_privs_role = InvalidOid;
+static List *cached_privs_roles = NIL;
+static Oid     cached_member_role = InvalidOid;
+static List *cached_membership_roles = NIL;
 
-static Oid convert_table_name(text *tablename);
+
+static const char *getid(const char *s, char *n);
+static void putid(char *p, const char *s);
+static Acl *allocacl(int n);
+static void check_acl(const Acl *acl);
+static const char *aclparse(const char *s, AclItem *aip);
+static bool aclitem_match(const AclItem *a1, const AclItem *a2);
+static int aclitemComparator(const void *arg1, const void *arg2);
+static void check_circularity(const Acl *old_acl, const AclItem *mod_aip,
+                                 Oid ownerId);
+static Acl *recursive_revoke(Acl *acl, Oid grantee, AclMode revoke_privs,
+                                Oid ownerId, DropBehavior behavior);
+static int     oidComparator(const void *arg1, const void *arg2);
+
+static AclMode convert_priv_string(text *priv_type_text);
+static AclMode convert_any_priv_string(text *priv_type_text,
+                                               const priv_map *privileges);
+
+static Oid     convert_table_name(text *tablename);
 static AclMode convert_table_priv_string(text *priv_type_text);
-static Oid convert_database_name(text *databasename);
+static AclMode convert_sequence_priv_string(text *priv_type_text);
+static AttrNumber convert_column_name(Oid tableoid, text *column);
+static AclMode convert_column_priv_string(text *priv_type_text);
+static Oid     convert_database_name(text *databasename);
 static AclMode convert_database_priv_string(text *priv_type_text);
-static Oid convert_function_name(text *functionname);
+static Oid     convert_foreign_data_wrapper_name(text *fdwname);
+static AclMode convert_foreign_data_wrapper_priv_string(text *priv_type_text);
+static Oid     convert_function_name(text *functionname);
 static AclMode convert_function_priv_string(text *priv_type_text);
-static Oid convert_language_name(text *languagename);
+static Oid     convert_language_name(text *languagename);
 static AclMode convert_language_priv_string(text *priv_type_text);
-static Oid convert_schema_name(text *schemaname);
+static Oid     convert_schema_name(text *schemaname);
 static AclMode convert_schema_priv_string(text *priv_type_text);
+static Oid     convert_server_name(text *servername);
+static AclMode convert_server_priv_string(text *priv_type_text);
+static Oid     convert_tablespace_name(text *tablespacename);
+static AclMode convert_tablespace_priv_string(text *priv_type_text);
+static AclMode convert_role_priv_string(text *priv_type_text);
+static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
+
+static void RoleMembershipCacheCallback(Datum arg, int cacheid, ItemPointer tuplePtr);
 
 
 /*
@@ -56,56 +123,101 @@ static AclMode convert_schema_priv_string(text *priv_type_text);
  * RETURNS:
  *             the string position in 's' that points to the next non-space character
  *             in 's', after any quotes.  Also:
- *             - loads the identifier into 'name'.  (If no identifier is found, 'name'
- *               contains an empty string.)  name must be NAMEDATALEN bytes.
+ *             - loads the identifier into 'n'.  (If no identifier is found, 'n'
+ *               contains an empty string.)  'n' must be NAMEDATALEN bytes.
  */
 static const char *
 getid(const char *s, char *n)
 {
-       unsigned        len;
-       const char *id;
-       int                     in_quotes = 0;
+       int                     len = 0;
+       bool            in_quotes = false;
 
        Assert(s && n);
 
        while (isspace((unsigned char) *s))
-               ++s;
-
-       if (*s == '"')
-       {
-               in_quotes = 1;
                s++;
-       }
-
-       for (id = s, len = 0;
-                isalnum((unsigned char) *s) || *s == '_' || in_quotes;
-                ++len, ++s)
+       /* This code had better match what putid() does, below */
+       for (;
+                *s != '\0' &&
+                (isalnum((unsigned char) *s) ||
+                 *s == '_' ||
+                 *s == '"' ||
+                 in_quotes);
+                s++)
        {
-               if (in_quotes && *s == '"')
+               if (*s == '"')
                {
-                       len--;
-                       in_quotes = 0;
+                       /* safe to look at next char (could be '\0' though) */
+                       if (*(s + 1) != '"')
+                       {
+                               in_quotes = !in_quotes;
+                               continue;
+                       }
+                       /* it's an escaped double quote; skip the escaping char */
+                       s++;
                }
+
+               /* Add the character to the string */
+               if (len >= NAMEDATALEN - 1)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NAME_TOO_LONG),
+                                        errmsg("identifier too long"),
+                                        errdetail("Identifier must be less than %d characters.",
+                                                          NAMEDATALEN)));
+
+               n[len++] = *s;
        }
-       if (len >= NAMEDATALEN)
-               elog(ERROR, "getid: identifier must be <%d characters",
-                        NAMEDATALEN);
-       if (len > 0)
-               memmove(n, id, len);
        n[len] = '\0';
        while (isspace((unsigned char) *s))
-               ++s;
+               s++;
        return s;
 }
 
+/*
+ * Write a role name at *p, adding double quotes if needed.
+ * There must be at least (2*NAMEDATALEN)+2 bytes available at *p.
+ * This needs to be kept in sync with copyAclUserName in pg_dump/dumputils.c
+ */
+static void
+putid(char *p, const char *s)
+{
+       const char *src;
+       bool            safe = true;
+
+       for (src = s; *src; src++)
+       {
+               /* This test had better match what getid() does, above */
+               if (!isalnum((unsigned char) *src) && *src != '_')
+               {
+                       safe = false;
+                       break;
+               }
+       }
+       if (!safe)
+               *p++ = '"';
+       for (src = s; *src; src++)
+       {
+               /* A double quote character in a username is encoded as "" */
+               if (*src == '"')
+                       *p++ = '"';
+               *p++ = *src;
+       }
+       if (!safe)
+               *p++ = '"';
+       *p = '\0';
+}
+
 /*
  * aclparse
  *             Consumes and parses an ACL specification of the form:
- *                             [group|user] [A-Za-z0-9]*[+-=][rwaR]*
+ *                             [group|user] [A-Za-z0-9]*=[rwaR]*
  *             from string 's', ignoring any leading white space or white space
  *             between the optional id type keyword (group|user) and the actual
  *             ACL specification.
  *
+ *             The group|user decoration is unnecessary in the roles world,
+ *             but we still accept it for backward compatibility.
+ *
  *             This routine is called by the parser as well as aclitemin(), hence
  *             the added generality.
  *
@@ -114,149 +226,326 @@ getid(const char *s, char *n)
  *             specification.  Also:
  *             - loads the structure pointed to by 'aip' with the appropriate
  *               UID/GID, id type identifier and mode type values.
- *             - loads 'modechg' with the mode change flag.
  */
 static const char *
-aclparse(const char *s, AclItem *aip, unsigned *modechg)
+aclparse(const char *s, AclItem *aip)
 {
-       AclMode         privs;
-       uint32          idtype;
+       AclMode         privs,
+                               goption,
+                               read;
        char            name[NAMEDATALEN];
+       char            name2[NAMEDATALEN];
 
-       Assert(s && aip && modechg);
+       Assert(s && aip);
 
 #ifdef ACLDEBUG
-       elog(LOG, "aclparse: input = '%s'", s);
+       elog(LOG, "aclparse: input = \"%s\"", s);
 #endif
-       idtype = ACL_IDTYPE_UID;
        s = getid(s, name);
-       if (*s != ACL_MODECHG_ADD_CHR &&
-               *s != ACL_MODECHG_DEL_CHR &&
-               *s != ACL_MODECHG_EQL_CHR)
+       if (*s != '=')
        {
                /* we just read a keyword, not a name */
-               if (strcmp(name, ACL_IDTYPE_GID_KEYWORD) == 0)
-                       idtype = ACL_IDTYPE_GID;
-               else if (strcmp(name, ACL_IDTYPE_UID_KEYWORD) != 0)
-                       elog(ERROR, "aclparse: bad keyword, must be [group|user]");
+               if (strcmp(name, "group") != 0 && strcmp(name, "user") != 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                        errmsg("unrecognized key word: \"%s\"", name),
+                                        errhint("ACL key word must be \"group\" or \"user\".")));
                s = getid(s, name);             /* move s to the name beyond the keyword */
                if (name[0] == '\0')
-                       elog(ERROR, "aclparse: a name must follow the [group|user] keyword");
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                        errmsg("missing name"),
+                                        errhint("A name must follow the \"group\" or \"user\" key word.")));
        }
-       if (name[0] == '\0')
-               idtype = ACL_IDTYPE_WORLD;
 
-       switch (*s)
-       {
-               case ACL_MODECHG_ADD_CHR:
-                       *modechg = ACL_MODECHG_ADD;
-                       break;
-               case ACL_MODECHG_DEL_CHR:
-                       *modechg = ACL_MODECHG_DEL;
-                       break;
-               case ACL_MODECHG_EQL_CHR:
-                       *modechg = ACL_MODECHG_EQL;
-                       break;
-               default:
-                       elog(ERROR, "aclparse: mode change flag must use \"%c%c%c\"",
-                                ACL_MODECHG_ADD_CHR,
-                                ACL_MODECHG_DEL_CHR,
-                                ACL_MODECHG_EQL_CHR);
-       }
+       if (*s != '=')
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                errmsg("missing \"=\" sign")));
 
-       privs = ACL_NO_RIGHTS;
+       privs = goption = ACL_NO_RIGHTS;
 
-       while (isalpha((unsigned char) *++s))
+       for (++s, read = 0; isalpha((unsigned char) *s) || *s == '*'; s++)
        {
                switch (*s)
                {
+                       case '*':
+                               goption |= read;
+                               break;
                        case ACL_INSERT_CHR:
-                               privs |= ACL_INSERT;
+                               read = ACL_INSERT;
                                break;
                        case ACL_SELECT_CHR:
-                               privs |= ACL_SELECT;
+                               read = ACL_SELECT;
                                break;
                        case ACL_UPDATE_CHR:
-                               privs |= ACL_UPDATE;
+                               read = ACL_UPDATE;
                                break;
                        case ACL_DELETE_CHR:
-                               privs |= ACL_DELETE;
+                               read = ACL_DELETE;
                                break;
-                       case ACL_RULE_CHR:
-                               privs |= ACL_RULE;
+                       case ACL_TRUNCATE_CHR:
+                               read = ACL_TRUNCATE;
                                break;
                        case ACL_REFERENCES_CHR:
-                               privs |= ACL_REFERENCES;
+                               read = ACL_REFERENCES;
                                break;
                        case ACL_TRIGGER_CHR:
-                               privs |= ACL_TRIGGER;
+                               read = ACL_TRIGGER;
                                break;
                        case ACL_EXECUTE_CHR:
-                               privs |= ACL_EXECUTE;
+                               read = ACL_EXECUTE;
                                break;
                        case ACL_USAGE_CHR:
-                               privs |= ACL_USAGE;
+                               read = ACL_USAGE;
                                break;
                        case ACL_CREATE_CHR:
-                               privs |= ACL_CREATE;
+                               read = ACL_CREATE;
                                break;
                        case ACL_CREATE_TEMP_CHR:
-                               privs |= ACL_CREATE_TEMP;
+                               read = ACL_CREATE_TEMP;
+                               break;
+                       case ACL_CONNECT_CHR:
+                               read = ACL_CONNECT;
+                               break;
+                       case 'R':                       /* ignore old RULE privileges */
+                               read = 0;
                                break;
                        default:
-                               elog(ERROR, "aclparse: mode flags must use \"%s\"",
-                                        ACL_ALL_RIGHTS_STR);
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                         errmsg("invalid mode character: must be one of \"%s\"",
+                                                        ACL_ALL_RIGHTS_STR)));
                }
+
+               privs |= read;
        }
 
-       switch (idtype)
+       if (name[0] == '\0')
+               aip->ai_grantee = ACL_ID_PUBLIC;
+       else
+               aip->ai_grantee = get_roleid_checked(name);
+
+       /*
+        * XXX Allow a degree of backward compatibility by defaulting the grantor
+        * to the superuser.
+        */
+       if (*s == '/')
        {
-               case ACL_IDTYPE_UID:
-                       aip->ai_id = get_usesysid(name);
-                       break;
-               case ACL_IDTYPE_GID:
-                       aip->ai_id = get_grosysid(name);
-                       break;
-               case ACL_IDTYPE_WORLD:
-                       aip->ai_id = ACL_ID_WORLD;
-                       break;
+               s = getid(s + 1, name2);
+               if (name2[0] == '\0')
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                        errmsg("a name must follow the \"/\" sign")));
+               aip->ai_grantor = get_roleid_checked(name2);
+       }
+       else
+       {
+               aip->ai_grantor = BOOTSTRAP_SUPERUSERID;
+               ereport(WARNING,
+                               (errcode(ERRCODE_INVALID_GRANTOR),
+                                errmsg("defaulting grantor to user ID %u",
+                                               BOOTSTRAP_SUPERUSERID)));
        }
 
-       ACLITEM_SET_PRIVS_IDTYPE(*aip, privs, idtype);
+       ACLITEM_SET_PRIVS_GOPTIONS(*aip, privs, goption);
 
 #ifdef ACLDEBUG
-       elog(LOG, "aclparse: correctly read [%x %d %x], modechg=%x",
-                idtype, aip->ai_id, privs, *modechg);
+       elog(LOG, "aclparse: correctly read [%u %x %x]",
+                aip->ai_grantee, privs, goption);
 #endif
+
        return s;
 }
 
 /*
- * makeacl
+ * allocacl
  *             Allocates storage for a new Acl with 'n' entries.
  *
  * RETURNS:
  *             the new Acl
  */
 static Acl *
-makeacl(int n)
+allocacl(int n)
 {
        Acl                *new_acl;
        Size            size;
 
        if (n < 0)
-               elog(ERROR, "makeacl: invalid size: %d", n);
+               elog(ERROR, "invalid size: %d", n);
        size = ACL_N_SIZE(n);
-       new_acl = (Acl *) palloc(size);
-       MemSet((char *) new_acl, 0, size);
-       new_acl->size = size;
+       new_acl = (Acl *) palloc0(size);
+       SET_VARSIZE(new_acl, size);
        new_acl->ndim = 1;
-       new_acl->flags = 0;
-       ARR_LBOUND(new_acl)[0] = 0;
+       new_acl->dataoffset = 0;        /* we never put in any nulls */
+       new_acl->elemtype = ACLITEMOID;
+       ARR_LBOUND(new_acl)[0] = 1;
        ARR_DIMS(new_acl)[0] = n;
        return new_acl;
 }
 
+/*
+ * Create a zero-entry ACL
+ */
+Acl *
+make_empty_acl(void)
+{
+       return allocacl(0);
+}
+
+/*
+ * Copy an ACL
+ */
+Acl *
+aclcopy(const Acl *orig_acl)
+{
+       Acl                *result_acl;
+
+       result_acl = allocacl(ACL_NUM(orig_acl));
+
+       memcpy(ACL_DAT(result_acl),
+                  ACL_DAT(orig_acl),
+                  ACL_NUM(orig_acl) * sizeof(AclItem));
+
+       return result_acl;
+}
+
+/*
+ * Concatenate two ACLs
+ *
+ * This is a bit cheesy, since we may produce an ACL with redundant entries.
+ * Be careful what the result is used for!
+ */
+Acl *
+aclconcat(const Acl *left_acl, const Acl *right_acl)
+{
+       Acl                *result_acl;
+
+       result_acl = allocacl(ACL_NUM(left_acl) + ACL_NUM(right_acl));
+
+       memcpy(ACL_DAT(result_acl),
+                  ACL_DAT(left_acl),
+                  ACL_NUM(left_acl) * sizeof(AclItem));
+
+       memcpy(ACL_DAT(result_acl) + ACL_NUM(left_acl),
+                  ACL_DAT(right_acl),
+                  ACL_NUM(right_acl) * sizeof(AclItem));
+
+       return result_acl;
+}
+
+/*
+ * Merge two ACLs
+ *
+ * This produces a properly merged ACL with no redundant entries.
+ * Returns NULL on NULL input.
+ */
+Acl *
+aclmerge(const Acl *left_acl, const Acl *right_acl, Oid ownerId)
+{
+       Acl                *result_acl;
+       AclItem    *aip;
+       int                     i,
+                               num;
+
+       /* Check for cases where one or both are empty/null */
+       if (left_acl == NULL || ACL_NUM(left_acl) == 0)
+       {
+               if (right_acl == NULL || ACL_NUM(right_acl) == 0)
+                       return NULL;
+               else
+                       return aclcopy(right_acl);
+       }
+       else
+       {
+               if (right_acl == NULL || ACL_NUM(right_acl) == 0)
+                       return aclcopy(left_acl);
+       }
+
+       /* Merge them the hard way, one item at a time */
+       result_acl = aclcopy(left_acl);
+
+       aip = ACL_DAT(right_acl);
+       num = ACL_NUM(right_acl);
+
+       for (i = 0; i < num; i++, aip++)
+       {
+               Acl *tmp_acl;
+
+               tmp_acl = aclupdate(result_acl, aip, ACL_MODECHG_ADD,
+                                                       ownerId, DROP_RESTRICT);
+               pfree(result_acl);
+               result_acl = tmp_acl;
+       }
+
+       return result_acl;
+}
+
+/*
+ * Sort the items in an ACL (into an arbitrary but consistent order)
+ */
+void
+aclitemsort(Acl *acl)
+{
+       if (acl != NULL && ACL_NUM(acl) > 1)
+               qsort(ACL_DAT(acl), ACL_NUM(acl), sizeof(AclItem), aclitemComparator);
+}
+
+/*
+ * Check if two ACLs are exactly equal
+ *
+ * This will not detect equality if the two arrays contain the same items
+ * in different orders.  To handle that case, sort both inputs first,
+ * using aclitemsort().
+ */
+bool
+aclequal(const Acl *left_acl, const Acl *right_acl)
+{
+       /* Check for cases where one or both are empty/null */
+       if (left_acl == NULL || ACL_NUM(left_acl) == 0)
+       {
+               if (right_acl == NULL || ACL_NUM(right_acl) == 0)
+                       return true;
+               else
+                       return false;
+       }
+       else
+       {
+               if (right_acl == NULL || ACL_NUM(right_acl) == 0)
+                       return false;
+       }
+
+       if (ACL_NUM(left_acl) != ACL_NUM(right_acl))
+               return false;
+
+       if (memcmp(ACL_DAT(left_acl),
+                          ACL_DAT(right_acl),
+                          ACL_NUM(left_acl) * sizeof(AclItem)) == 0)
+               return true;
+
+       return false;
+}
+
+/*
+ * Verify that an ACL array is acceptable (one-dimensional and has no nulls)
+ */
+static void
+check_acl(const Acl *acl)
+{
+       if (ARR_ELEMTYPE(acl) != ACLITEMOID)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("ACL array contains wrong data type")));
+       if (ARR_NDIM(acl) != 1)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("ACL arrays must be one-dimensional")));
+       if (ARR_HASNULL(acl))
+               ereport(ERROR,
+                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                errmsg("ACL arrays must not contain null values")));
+}
+
 /*
  * aclitemin
  *             Allocates storage for, and fills in, a new AclItem given a string
@@ -270,16 +559,16 @@ aclitemin(PG_FUNCTION_ARGS)
 {
        const char *s = PG_GETARG_CSTRING(0);
        AclItem    *aip;
-       unsigned        modechg;
 
        aip = (AclItem *) palloc(sizeof(AclItem));
-       s = aclparse(s, aip, &modechg);
-       if (modechg != ACL_MODECHG_EQL)
-               elog(ERROR, "aclitemin: cannot accept anything but = ACLs");
+       s = aclparse(s, aip);
        while (isspace((unsigned char) *s))
                ++s;
        if (*s)
-               elog(ERROR, "aclitemin: extra garbage at end of specification");
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                          errmsg("extra garbage at the end of the ACL specification")));
+
        PG_RETURN_ACLITEM_P(aip);
 }
 
@@ -299,94 +588,131 @@ aclitemout(PG_FUNCTION_ARGS)
        char       *out;
        HeapTuple       htup;
        unsigned        i;
-       char       *tmpname;
 
-       p = out = palloc(strlen("group = ") + N_ACL_RIGHTS + NAMEDATALEN + 1);
+       out = palloc(strlen("=/") +
+                                2 * N_ACL_RIGHTS +
+                                2 * (2 * NAMEDATALEN + 2) +
+                                1);
+
+       p = out;
        *p = '\0';
 
-       switch (ACLITEM_GET_IDTYPE(*aip))
+       if (aip->ai_grantee != ACL_ID_PUBLIC)
        {
-               case ACL_IDTYPE_UID:
-                       htup = SearchSysCache(SHADOWSYSID,
-                                                                 ObjectIdGetDatum(aip->ai_id),
-                                                                 0, 0, 0);
-                       if (HeapTupleIsValid(htup))
-                       {
-                               strncat(p,
-                                       NameStr(((Form_pg_shadow) GETSTRUCT(htup))->usename),
-                                               NAMEDATALEN);
-                               ReleaseSysCache(htup);
-                       }
-                       else
-                       {
-                               /* Generate numeric UID if we don't find an entry */
-                               char       *tmp;
-
-                               tmp = DatumGetCString(DirectFunctionCall1(int4out,
-                                                                        Int32GetDatum((int32) aip->ai_id)));
-                               strcat(p, tmp);
-                               pfree(tmp);
-                       }
-                       break;
-               case ACL_IDTYPE_GID:
-                       strcat(p, "group ");
-                       tmpname = get_groname(aip->ai_id);
-                       if (tmpname != NULL)
-                               strncat(p, tmpname, NAMEDATALEN);
-                       else
-                       {
-                               /* Generate numeric GID if we don't find an entry */
-                               char       *tmp;
-
-                               tmp = DatumGetCString(DirectFunctionCall1(int4out,
-                                                                        Int32GetDatum((int32) aip->ai_id)));
-                               strcat(p, tmp);
-                               pfree(tmp);
-                       }
-                       break;
-               case ACL_IDTYPE_WORLD:
-                       break;
-               default:
-                       elog(ERROR, "aclitemout: bad idtype: %d",
-                                ACLITEM_GET_IDTYPE(*aip));
-                       break;
+               htup = SearchSysCache(AUTHOID,
+                                                         ObjectIdGetDatum(aip->ai_grantee),
+                                                         0, 0, 0);
+               if (HeapTupleIsValid(htup))
+               {
+                       putid(p, NameStr(((Form_pg_authid) GETSTRUCT(htup))->rolname));
+                       ReleaseSysCache(htup);
+               }
+               else
+               {
+                       /* Generate numeric OID if we don't find an entry */
+                       sprintf(p, "%u", aip->ai_grantee);
+               }
        }
        while (*p)
                ++p;
+
        *p++ = '=';
+
        for (i = 0; i < N_ACL_RIGHTS; ++i)
-               if (aip->ai_privs & (1 << i))
+       {
+               if (ACLITEM_GET_PRIVS(*aip) & (1 << i))
                        *p++ = ACL_ALL_RIGHTS_STR[i];
+               if (ACLITEM_GET_GOPTIONS(*aip) & (1 << i))
+                       *p++ = '*';
+       }
+
+       *p++ = '/';
        *p = '\0';
 
+       htup = SearchSysCache(AUTHOID,
+                                                 ObjectIdGetDatum(aip->ai_grantor),
+                                                 0, 0, 0);
+       if (HeapTupleIsValid(htup))
+       {
+               putid(p, NameStr(((Form_pg_authid) GETSTRUCT(htup))->rolname));
+               ReleaseSysCache(htup);
+       }
+       else
+       {
+               /* Generate numeric OID if we don't find an entry */
+               sprintf(p, "%u", aip->ai_grantor);
+       }
+
        PG_RETURN_CSTRING(out);
 }
 
 /*
- * aclitemeq
- * aclitemgt
- *             AclItem equality and greater-than comparison routines.
- *             Two AclItems are considered equal iff they have the same
- *             identifier (and identifier type); the privileges are ignored.
- *             Note that these routines are really only useful for sorting
- *             AclItems into identifier order.
- *
- * RETURNS:
- *             a boolean value indicating = or >
+ * aclitem_match
+ *             Two AclItems are considered to match iff they have the same
+ *             grantee and grantor; the privileges are ignored.
  */
 static bool
-aclitemeq(const AclItem *a1, const AclItem *a2)
+aclitem_match(const AclItem *a1, const AclItem *a2)
 {
-       return ACLITEM_GET_IDTYPE(*a1) == ACLITEM_GET_IDTYPE(*a2) &&
-               a1->ai_id == a2->ai_id;
+       return a1->ai_grantee == a2->ai_grantee &&
+               a1->ai_grantor == a2->ai_grantor;
 }
 
-static bool
-aclitemgt(const AclItem *a1, const AclItem *a2)
+/*
+ * aclitemComparator
+ *             qsort comparison function for AclItems
+ */
+static int
+aclitemComparator(const void *arg1, const void *arg2)
+{
+       const AclItem *a1 = (const AclItem *) arg1;
+       const AclItem *a2 = (const AclItem *) arg2;
+
+       if (a1->ai_grantee > a2->ai_grantee)
+               return 1;
+       if (a1->ai_grantee < a2->ai_grantee)
+               return -1;
+       if (a1->ai_grantor > a2->ai_grantor)
+               return 1;
+       if (a1->ai_grantor < a2->ai_grantor)
+               return -1;
+       if (a1->ai_privs > a2->ai_privs)
+               return 1;
+       if (a1->ai_privs < a2->ai_privs)
+               return -1;
+       return 0;
+}
+
+/*
+ * aclitem equality operator
+ */
+Datum
+aclitem_eq(PG_FUNCTION_ARGS)
+{
+       AclItem    *a1 = PG_GETARG_ACLITEM_P(0);
+       AclItem    *a2 = PG_GETARG_ACLITEM_P(1);
+       bool            result;
+
+       result = a1->ai_privs == a2->ai_privs &&
+               a1->ai_grantee == a2->ai_grantee &&
+               a1->ai_grantor == a2->ai_grantor;
+       PG_RETURN_BOOL(result);
+}
+
+/*
+ * aclitem hash function
+ *
+ * We make aclitems hashable not so much because anyone is likely to hash
+ * them, as because we want array equality to work on aclitem arrays, and
+ * with the typcache mechanism we must have a hash or btree opclass.
+ */
+Datum
+hash_aclitem(PG_FUNCTION_ARGS)
 {
-       return ((ACLITEM_GET_IDTYPE(*a1) > ACLITEM_GET_IDTYPE(*a2)) ||
-                       (ACLITEM_GET_IDTYPE(*a1) == ACLITEM_GET_IDTYPE(*a2) &&
-                        a1->ai_id > a2->ai_id));
+       AclItem    *a = PG_GETARG_ACLITEM_P(0);
+
+       /* not very bright, but avoids any issue of padding in struct */
+       PG_RETURN_UINT32((uint32) (a->ai_privs + a->ai_grantee + a->ai_grantor));
 }
 
 
@@ -394,54 +720,109 @@ aclitemgt(const AclItem *a1, const AclItem *a2)
  * acldefault()  --- create an ACL describing default access permissions
  *
  * Change this routine if you want to alter the default access policy for
- * newly-created tables (or any table with a NULL acl entry in pg_class)
+ * newly-created objects (or any object with a NULL acl entry).
+ *
+ * Note that these are the hard-wired "defaults" that are used in the
+ * absence of any pg_default_acl entry.
  */
 Acl *
-acldefault(GrantObjectType objtype, AclId ownerid)
+acldefault(GrantObjectType objtype, Oid ownerId)
 {
        AclMode         world_default;
        AclMode         owner_default;
+       int                     nacl;
        Acl                *acl;
        AclItem    *aip;
 
        switch (objtype)
        {
+               case ACL_OBJECT_COLUMN:
+                       /* by default, columns have no extra privileges */
+                       world_default = ACL_NO_RIGHTS;
+                       owner_default = ACL_NO_RIGHTS;
+                       break;
                case ACL_OBJECT_RELATION:
                        world_default = ACL_NO_RIGHTS;
                        owner_default = ACL_ALL_RIGHTS_RELATION;
                        break;
-               case ACL_OBJECT_DATABASE:
+               case ACL_OBJECT_SEQUENCE:
                        world_default = ACL_NO_RIGHTS;
+                       owner_default = ACL_ALL_RIGHTS_SEQUENCE;
+                       break;
+               case ACL_OBJECT_DATABASE:
+                       /* for backwards compatibility, grant some rights by default */
+                       world_default = ACL_CREATE_TEMP | ACL_CONNECT;
                        owner_default = ACL_ALL_RIGHTS_DATABASE;
                        break;
                case ACL_OBJECT_FUNCTION:
-                       world_default = ACL_NO_RIGHTS;
+                       /* Grant EXECUTE by default, for now */
+                       world_default = ACL_EXECUTE;
                        owner_default = ACL_ALL_RIGHTS_FUNCTION;
                        break;
                case ACL_OBJECT_LANGUAGE:
-                       world_default = ACL_NO_RIGHTS;
+                       /* Grant USAGE by default, for now */
+                       world_default = ACL_USAGE;
                        owner_default = ACL_ALL_RIGHTS_LANGUAGE;
                        break;
+               case ACL_OBJECT_LARGEOBJECT:
+                       world_default = ACL_NO_RIGHTS;
+                       owner_default = ACL_ALL_RIGHTS_LARGEOBJECT;
+                       break;
                case ACL_OBJECT_NAMESPACE:
                        world_default = ACL_NO_RIGHTS;
                        owner_default = ACL_ALL_RIGHTS_NAMESPACE;
                        break;
+               case ACL_OBJECT_TABLESPACE:
+                       world_default = ACL_NO_RIGHTS;
+                       owner_default = ACL_ALL_RIGHTS_TABLESPACE;
+                       break;
+               case ACL_OBJECT_FDW:
+                       world_default = ACL_NO_RIGHTS;
+                       owner_default = ACL_ALL_RIGHTS_FDW;
+                       break;
+               case ACL_OBJECT_FOREIGN_SERVER:
+                       world_default = ACL_NO_RIGHTS;
+                       owner_default = ACL_ALL_RIGHTS_FOREIGN_SERVER;
+                       break;
                default:
-                       elog(ERROR, "acldefault: bogus objtype %d", (int) objtype);
-                       world_default = ACL_NO_RIGHTS; /* keep compiler quiet */
+                       elog(ERROR, "unrecognized objtype: %d", (int) objtype);
+                       world_default = ACL_NO_RIGHTS;          /* keep compiler quiet */
                        owner_default = ACL_NO_RIGHTS;
                        break;
        }
 
-       acl = makeacl(ownerid ? 2 : 1);
+       nacl = 0;
+       if (world_default != ACL_NO_RIGHTS)
+               nacl++;
+       if (owner_default != ACL_NO_RIGHTS)
+               nacl++;
+
+       acl = allocacl(nacl);
        aip = ACL_DAT(acl);
 
-       aip[0].ai_id = ACL_ID_WORLD;
-       ACLITEM_SET_PRIVS_IDTYPE(aip[0], world_default, ACL_IDTYPE_WORLD);
-       if (ownerid)
+       if (world_default != ACL_NO_RIGHTS)
+       {
+               aip->ai_grantee = ACL_ID_PUBLIC;
+               aip->ai_grantor = ownerId;
+               ACLITEM_SET_PRIVS_GOPTIONS(*aip, world_default, ACL_NO_RIGHTS);
+               aip++;
+       }
+
+       /*
+        * Note that the owner's entry shows all ordinary privileges but no grant
+        * options.  This is because his grant options come "from the system" and
+        * not from his own efforts.  (The SQL spec says that the owner's rights
+        * come from a "_SYSTEM" authid.)  However, we do consider that the
+        * owner's ordinary privileges are self-granted; this lets him revoke
+        * them.  We implement the owner's grant options without any explicit
+        * "_SYSTEM"-like ACL entry, by internally special-casing the owner
+        * whereever we are testing grant options.
+        */
+       if (owner_default != ACL_NO_RIGHTS)
        {
-               aip[1].ai_id = ownerid;
-               ACLITEM_SET_PRIVS_IDTYPE(aip[1], owner_default, ACL_IDTYPE_UID);
+               aip->ai_grantee = ownerId;
+               aip->ai_grantor = ownerId;
+               ACLITEM_SET_PRIVS_GOPTIONS(*aip, owner_default, ACL_NO_RIGHTS);
        }
 
        return acl;
@@ -449,1219 +830,4053 @@ acldefault(GrantObjectType objtype, AclId ownerid)
 
 
 /*
- * Add or replace an item in an ACL array.     The result is a modified copy;
- * the input object is not changed.
+ * Update an ACL array to add or remove specified privileges.
+ *
+ *     old_acl: the input ACL array
+ *     mod_aip: defines the privileges to be added, removed, or substituted
+ *     modechg: ACL_MODECHG_ADD, ACL_MODECHG_DEL, or ACL_MODECHG_EQL
+ *     ownerId: Oid of object owner
+ *     behavior: RESTRICT or CASCADE behavior for recursive removal
+ *
+ * ownerid and behavior are only relevant when the update operation specifies
+ * deletion of grant options.
+ *
+ * The result is a modified copy; the input object is not changed.
  *
  * NB: caller is responsible for having detoasted the input ACL, if needed.
  */
 Acl *
-aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg)
+aclupdate(const Acl *old_acl, const AclItem *mod_aip,
+                 int modechg, Oid ownerId, DropBehavior behavior)
 {
-       Acl                *new_acl;
+       Acl                *new_acl = NULL;
        AclItem    *old_aip,
-                          *new_aip;
+                          *new_aip = NULL;
+       AclMode         old_rights,
+                               old_goptions,
+                               new_rights,
+                               new_goptions;
        int                     dst,
                                num;
 
-       /* These checks for null input are probably dead code, but... */
-       if (!old_acl || ACL_NUM(old_acl) < 1)
-               old_acl = makeacl(1);
-       if (!mod_aip)
-       {
-               new_acl = makeacl(ACL_NUM(old_acl));
-               memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
-               return new_acl;
-       }
+       /* Caller probably already checked old_acl, but be safe */
+       check_acl(old_acl);
+
+       /* If granting grant options, check for circularity */
+       if (modechg != ACL_MODECHG_DEL &&
+               ACLITEM_GET_GOPTIONS(*mod_aip) != ACL_NO_RIGHTS)
+               check_circularity(old_acl, mod_aip, ownerId);
 
        num = ACL_NUM(old_acl);
        old_aip = ACL_DAT(old_acl);
 
        /*
-        * Search the ACL for an existing entry for 'id'.  If one exists, just
-        * modify the entry in-place (well, in the same position, since we
-        * actually return a copy); otherwise, insert the new entry in
-        * sort-order.
+        * Search the ACL for an existing entry for this grantee and grantor. If
+        * one exists, just modify the entry in-place (well, in the same position,
+        * since we actually return a copy); otherwise, insert the new entry at
+        * the end.
         */
-       /* find the first element not less than the element to be inserted */
-       for (dst = 0; dst < num && aclitemgt(mod_aip, old_aip + dst); ++dst)
-               ;
 
-       if (dst < num && aclitemeq(mod_aip, old_aip + dst))
+       for (dst = 0; dst < num; ++dst)
        {
-               /* found a match, so modify existing item */
-               new_acl = makeacl(num);
-               new_aip = ACL_DAT(new_acl);
-               memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
+               if (aclitem_match(mod_aip, old_aip + dst))
+               {
+                       /* found a match, so modify existing item */
+                       new_acl = allocacl(num);
+                       new_aip = ACL_DAT(new_acl);
+                       memcpy(new_acl, old_acl, ACL_SIZE(old_acl));
+                       break;
+               }
        }
-       else
+
+       if (dst == num)
        {
-               /* need to insert a new item */
-               new_acl = makeacl(num + 1);
+               /* need to append a new item */
+               new_acl = allocacl(num + 1);
                new_aip = ACL_DAT(new_acl);
-               if (dst == 0)
-               {                                               /* start */
-                       elog(ERROR, "aclinsert3: insertion before world ACL??");
-               }
-               else if (dst >= num)
-               {                                               /* end */
-                       memcpy((char *) new_aip,
-                                  (char *) old_aip,
-                                  num * sizeof(AclItem));
-               }
-               else
-               {                                               /* middle */
-                       memcpy((char *) new_aip,
-                                  (char *) old_aip,
-                                  dst * sizeof(AclItem));
-                       memcpy((char *) (new_aip + dst + 1),
-                                  (char *) (old_aip + dst),
-                                  (num - dst) * sizeof(AclItem));
-               }
+               memcpy(new_aip, old_aip, num * sizeof(AclItem));
+
                /* initialize the new entry with no permissions */
-               new_aip[dst].ai_id = mod_aip->ai_id;
-               ACLITEM_SET_PRIVS_IDTYPE(new_aip[dst], ACL_NO_RIGHTS,
-                                                                ACLITEM_GET_IDTYPE(*mod_aip));
+               new_aip[dst].ai_grantee = mod_aip->ai_grantee;
+               new_aip[dst].ai_grantor = mod_aip->ai_grantor;
+               ACLITEM_SET_PRIVS_GOPTIONS(new_aip[dst],
+                                                                  ACL_NO_RIGHTS, ACL_NO_RIGHTS);
                num++;                                  /* set num to the size of new_acl */
        }
 
-       /* apply the permissions mod */
+       old_rights = ACLITEM_GET_RIGHTS(new_aip[dst]);
+       old_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]);
+
+       /* apply the specified permissions change */
        switch (modechg)
        {
                case ACL_MODECHG_ADD:
-                       new_aip[dst].ai_privs |= ACLITEM_GET_PRIVS(*mod_aip);
+                       ACLITEM_SET_RIGHTS(new_aip[dst],
+                                                          old_rights | ACLITEM_GET_RIGHTS(*mod_aip));
                        break;
                case ACL_MODECHG_DEL:
-                       new_aip[dst].ai_privs &= ~ACLITEM_GET_PRIVS(*mod_aip);
+                       ACLITEM_SET_RIGHTS(new_aip[dst],
+                                                          old_rights & ~ACLITEM_GET_RIGHTS(*mod_aip));
                        break;
                case ACL_MODECHG_EQL:
-                       ACLITEM_SET_PRIVS_IDTYPE(new_aip[dst],
-                                                                        ACLITEM_GET_PRIVS(*mod_aip),
-                                                                        ACLITEM_GET_IDTYPE(new_aip[dst]));
+                       ACLITEM_SET_RIGHTS(new_aip[dst],
+                                                          ACLITEM_GET_RIGHTS(*mod_aip));
                        break;
        }
 
+       new_rights = ACLITEM_GET_RIGHTS(new_aip[dst]);
+       new_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]);
+
        /*
-        * if the adjusted entry has no permissions, delete it from the list.
-        * For example, this helps in removing entries for users who no longer
-        * exist.  EXCEPTION: never remove the world entry.
+        * If the adjusted entry has no permissions, delete it from the list.
         */
-       if (ACLITEM_GET_PRIVS(new_aip[dst]) == ACL_NO_RIGHTS && dst > 0)
+       if (new_rights == ACL_NO_RIGHTS)
        {
-               memmove((char *) (new_aip + dst),
-                               (char *) (new_aip + dst + 1),
+               memmove(new_aip + dst,
+                               new_aip + dst + 1,
                                (num - dst - 1) * sizeof(AclItem));
+               /* Adjust array size to be 'num - 1' items */
                ARR_DIMS(new_acl)[0] = num - 1;
-               ARR_SIZE(new_acl) -= sizeof(AclItem);
+               SET_VARSIZE(new_acl, ACL_N_SIZE(num - 1));
+       }
+
+       /*
+        * Remove abandoned privileges (cascading revoke).      Currently we can only
+        * handle this when the grantee is not PUBLIC.
+        */
+       if ((old_goptions & ~new_goptions) != 0)
+       {
+               Assert(mod_aip->ai_grantee != ACL_ID_PUBLIC);
+               new_acl = recursive_revoke(new_acl, mod_aip->ai_grantee,
+                                                                  (old_goptions & ~new_goptions),
+                                                                  ownerId, behavior);
        }
 
        return new_acl;
 }
 
 /*
- * aclinsert (exported function)
+ * Update an ACL array to reflect a change of owner to the parent object
+ *
+ *     old_acl: the input ACL array (must not be NULL)
+ *     oldOwnerId: Oid of the old object owner
+ *     newOwnerId: Oid of the new object owner
+ *
+ * The result is a modified copy; the input object is not changed.
+ *
+ * NB: caller is responsible for having detoasted the input ACL, if needed.
  */
-Datum
-aclinsert(PG_FUNCTION_ARGS)
-{
-       Acl                *old_acl = PG_GETARG_ACL_P(0);
-       AclItem    *mod_aip = PG_GETARG_ACLITEM_P(1);
-
-       PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL));
-}
-
-Datum
-aclremove(PG_FUNCTION_ARGS)
+Acl *
+aclnewowner(const Acl *old_acl, Oid oldOwnerId, Oid newOwnerId)
 {
-       Acl                *old_acl = PG_GETARG_ACL_P(0);
-       AclItem    *mod_aip = PG_GETARG_ACLITEM_P(1);
        Acl                *new_acl;
-       AclItem    *old_aip,
-                          *new_aip;
+       AclItem    *new_aip;
+       AclItem    *old_aip;
+       AclItem    *dst_aip;
+       AclItem    *src_aip;
+       AclItem    *targ_aip;
+       bool            newpresent = false;
        int                     dst,
-                               old_num,
-                               new_num;
+                               src,
+                               targ,
+                               num;
 
-       /* These checks for null input should be dead code, but... */
-       if (!old_acl || ACL_NUM(old_acl) < 1)
-               old_acl = makeacl(1);
-       if (!mod_aip)
-       {
-               new_acl = makeacl(ACL_NUM(old_acl));
-               memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
-               PG_RETURN_ACL_P(new_acl);
-       }
+       check_acl(old_acl);
 
-       old_num = ACL_NUM(old_acl);
+       /*
+        * Make a copy of the given ACL, substituting new owner ID for old
+        * wherever it appears as either grantor or grantee.  Also note if the new
+        * owner ID is already present.
+        */
+       num = ACL_NUM(old_acl);
        old_aip = ACL_DAT(old_acl);
-
-       /* Search for the matching entry */
-       for (dst = 0; dst < old_num && !aclitemeq(mod_aip, old_aip + dst); ++dst)
-               ;
-
-       if (dst >= old_num)
+       new_acl = allocacl(num);
+       new_aip = ACL_DAT(new_acl);
+       memcpy(new_aip, old_aip, num * sizeof(AclItem));
+       for (dst = 0, dst_aip = new_aip; dst < num; dst++, dst_aip++)
        {
-               /* Not found, so return copy of source ACL */
-               new_acl = makeacl(old_num);
-               memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
+               if (dst_aip->ai_grantor == oldOwnerId)
+                       dst_aip->ai_grantor = newOwnerId;
+               else if (dst_aip->ai_grantor == newOwnerId)
+                       newpresent = true;
+               if (dst_aip->ai_grantee == oldOwnerId)
+                       dst_aip->ai_grantee = newOwnerId;
+               else if (dst_aip->ai_grantee == newOwnerId)
+                       newpresent = true;
        }
-       else
+
+       /*
+        * If the old ACL contained any references to the new owner, then we may
+        * now have generated an ACL containing duplicate entries.      Find them and
+        * merge them so that there are not duplicates.  (This is relatively
+        * expensive since we use a stupid O(N^2) algorithm, but it's unlikely to
+        * be the normal case.)
+        *
+        * To simplify deletion of duplicate entries, we temporarily leave them in
+        * the array but set their privilege masks to zero; when we reach such an
+        * entry it's just skipped.  (Thus, a side effect of this code will be to
+        * remove privilege-free entries, should there be any in the input.)  dst
+        * is the next output slot, targ is the currently considered input slot
+        * (always >= dst), and src scans entries to the right of targ looking for
+        * duplicates.  Once an entry has been emitted to dst it is known
+        * duplicate-free and need not be considered anymore.
+        */
+       if (newpresent)
        {
-               new_num = old_num - 1;
-               new_acl = makeacl(new_num);
-               new_aip = ACL_DAT(new_acl);
-               if (dst == 0)
-               {                                               /* start */
-                       elog(ERROR, "aclremove: removal of the world ACL??");
+               dst = 0;
+               for (targ = 0, targ_aip = new_aip; targ < num; targ++, targ_aip++)
+               {
+                       /* ignore if deleted in an earlier pass */
+                       if (ACLITEM_GET_RIGHTS(*targ_aip) == ACL_NO_RIGHTS)
+                               continue;
+                       /* find and merge any duplicates */
+                       for (src = targ + 1, src_aip = targ_aip + 1; src < num;
+                                src++, src_aip++)
+                       {
+                               if (ACLITEM_GET_RIGHTS(*src_aip) == ACL_NO_RIGHTS)
+                                       continue;
+                               if (aclitem_match(targ_aip, src_aip))
+                               {
+                                       ACLITEM_SET_RIGHTS(*targ_aip,
+                                                                          ACLITEM_GET_RIGHTS(*targ_aip) |
+                                                                          ACLITEM_GET_RIGHTS(*src_aip));
+                                       /* mark the duplicate deleted */
+                                       ACLITEM_SET_RIGHTS(*src_aip, ACL_NO_RIGHTS);
+                               }
+                       }
+                       /* and emit to output */
+                       new_aip[dst] = *targ_aip;
+                       dst++;
                }
-               else if (dst == old_num - 1)
-               {                                               /* end */
-                       memcpy((char *) new_aip,
-                                  (char *) old_aip,
-                                  new_num * sizeof(AclItem));
+               /* Adjust array size to be 'dst' items */
+               ARR_DIMS(new_acl)[0] = dst;
+               SET_VARSIZE(new_acl, ACL_N_SIZE(dst));
+       }
+
+       return new_acl;
+}
+
+
+/*
+ * When granting grant options, we must disallow attempts to set up circular
+ * chains of grant options.  Suppose A (the object owner) grants B some
+ * privileges with grant option, and B re-grants them to C.  If C could
+ * grant the privileges to B as well, then A would be unable to effectively
+ * revoke the privileges from B, since recursive_revoke would consider that
+ * B still has 'em from C.
+ *
+ * We check for this by recursively deleting all grant options belonging to
+ * the target grantee, and then seeing if the would-be grantor still has the
+ * grant option or not.
+ */
+static void
+check_circularity(const Acl *old_acl, const AclItem *mod_aip,
+                                 Oid ownerId)
+{
+       Acl                *acl;
+       AclItem    *aip;
+       int                     i,
+                               num;
+       AclMode         own_privs;
+
+       check_acl(old_acl);
+
+       /*
+        * For now, grant options can only be granted to roles, not PUBLIC.
+        * Otherwise we'd have to work a bit harder here.
+        */
+       Assert(mod_aip->ai_grantee != ACL_ID_PUBLIC);
+
+       /* The owner always has grant options, no need to check */
+       if (mod_aip->ai_grantor == ownerId)
+               return;
+
+       /* Make a working copy */
+       acl = allocacl(ACL_NUM(old_acl));
+       memcpy(acl, old_acl, ACL_SIZE(old_acl));
+
+       /* Zap all grant options of target grantee, plus what depends on 'em */
+cc_restart:
+       num = ACL_NUM(acl);
+       aip = ACL_DAT(acl);
+       for (i = 0; i < num; i++)
+       {
+               if (aip[i].ai_grantee == mod_aip->ai_grantee &&
+                       ACLITEM_GET_GOPTIONS(aip[i]) != ACL_NO_RIGHTS)
+               {
+                       Acl                *new_acl;
+
+                       /* We'll actually zap ordinary privs too, but no matter */
+                       new_acl = aclupdate(acl, &aip[i], ACL_MODECHG_DEL,
+                                                               ownerId, DROP_CASCADE);
+
+                       pfree(acl);
+                       acl = new_acl;
+
+                       goto cc_restart;
                }
-               else
-               {                                               /* middle */
-                       memcpy((char *) new_aip,
-                                  (char *) old_aip,
-                                  dst * sizeof(AclItem));
-                       memcpy((char *) (new_aip + dst),
-                                  (char *) (old_aip + dst + 1),
-                                  (new_num - dst) * sizeof(AclItem));
+       }
+
+       /* Now we can compute grantor's independently-derived privileges */
+       own_privs = aclmask(acl,
+                                               mod_aip->ai_grantor,
+                                               ownerId,
+                                               ACL_GRANT_OPTION_FOR(ACLITEM_GET_GOPTIONS(*mod_aip)),
+                                               ACLMASK_ALL);
+       own_privs = ACL_OPTION_TO_PRIVS(own_privs);
+
+       if ((ACLITEM_GET_GOPTIONS(*mod_aip) & ~own_privs) != 0)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+               errmsg("grant options cannot be granted back to your own grantor")));
+
+       pfree(acl);
+}
+
+
+/*
+ * Ensure that no privilege is "abandoned".  A privilege is abandoned
+ * if the user that granted the privilege loses the grant option.  (So
+ * the chain through which it was granted is broken.)  Either the
+ * abandoned privileges are revoked as well, or an error message is
+ * printed, depending on the drop behavior option.
+ *
+ *     acl: the input ACL list
+ *     grantee: the user from whom some grant options have been revoked
+ *     revoke_privs: the grant options being revoked
+ *     ownerId: Oid of object owner
+ *     behavior: RESTRICT or CASCADE behavior for recursive removal
+ *
+ * The input Acl object is pfree'd if replaced.
+ */
+static Acl *
+recursive_revoke(Acl *acl,
+                                Oid grantee,
+                                AclMode revoke_privs,
+                                Oid ownerId,
+                                DropBehavior behavior)
+{
+       AclMode         still_has;
+       AclItem    *aip;
+       int                     i,
+                               num;
+
+       check_acl(acl);
+
+       /* The owner can never truly lose grant options, so short-circuit */
+       if (grantee == ownerId)
+               return acl;
+
+       /* The grantee might still have the privileges via another grantor */
+       still_has = aclmask(acl, grantee, ownerId,
+                                               ACL_GRANT_OPTION_FOR(revoke_privs),
+                                               ACLMASK_ALL);
+       revoke_privs &= ~still_has;
+       if (revoke_privs == ACL_NO_RIGHTS)
+               return acl;
+
+restart:
+       num = ACL_NUM(acl);
+       aip = ACL_DAT(acl);
+       for (i = 0; i < num; i++)
+       {
+               if (aip[i].ai_grantor == grantee
+                       && (ACLITEM_GET_PRIVS(aip[i]) & revoke_privs) != 0)
+               {
+                       AclItem         mod_acl;
+                       Acl                *new_acl;
+
+                       if (behavior == DROP_RESTRICT)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+                                                errmsg("dependent privileges exist"),
+                                                errhint("Use CASCADE to revoke them too.")));
+
+                       mod_acl.ai_grantor = grantee;
+                       mod_acl.ai_grantee = aip[i].ai_grantee;
+                       ACLITEM_SET_PRIVS_GOPTIONS(mod_acl,
+                                                                          revoke_privs,
+                                                                          revoke_privs);
+
+                       new_acl = aclupdate(acl, &mod_acl, ACL_MODECHG_DEL,
+                                                               ownerId, behavior);
+
+                       pfree(acl);
+                       acl = new_acl;
+
+                       goto restart;
+               }
+       }
+
+       return acl;
+}
+
+
+/*
+ * aclmask --- compute bitmask of all privileges held by roleid.
+ *
+ * When 'how' = ACLMASK_ALL, this simply returns the privilege bits
+ * held by the given roleid according to the given ACL list, ANDed
+ * with 'mask'.  (The point of passing 'mask' is to let the routine
+ * exit early if all privileges of interest have been found.)
+ *
+ * When 'how' = ACLMASK_ANY, returns as soon as any bit in the mask
+ * is known true.  (This lets us exit soonest in cases where the
+ * caller is only going to test for zero or nonzero result.)
+ *
+ * Usage patterns:
+ *
+ * To see if any of a set of privileges are held:
+ *             if (aclmask(acl, roleid, ownerId, privs, ACLMASK_ANY) != 0)
+ *
+ * To see if all of a set of privileges are held:
+ *             if (aclmask(acl, roleid, ownerId, privs, ACLMASK_ALL) == privs)
+ *
+ * To determine exactly which of a set of privileges are held:
+ *             heldprivs = aclmask(acl, roleid, ownerId, privs, ACLMASK_ALL);
+ */
+AclMode
+aclmask(const Acl *acl, Oid roleid, Oid ownerId,
+               AclMode mask, AclMaskHow how)
+{
+       AclMode         result;
+       AclMode         remaining;
+       AclItem    *aidat;
+       int                     i,
+                               num;
+
+       /*
+        * Null ACL should not happen, since caller should have inserted
+        * appropriate default
+        */
+       if (acl == NULL)
+               elog(ERROR, "null ACL");
+
+       check_acl(acl);
+
+       /* Quick exit for mask == 0 */
+       if (mask == 0)
+               return 0;
+
+       result = 0;
+
+       /* Owner always implicitly has all grant options */
+       if ((mask & ACLITEM_ALL_GOPTION_BITS) &&
+               has_privs_of_role(roleid, ownerId))
+       {
+               result = mask & ACLITEM_ALL_GOPTION_BITS;
+               if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
+                       return result;
+       }
+
+       num = ACL_NUM(acl);
+       aidat = ACL_DAT(acl);
+
+       /*
+        * Check privileges granted directly to roleid or to public
+        */
+       for (i = 0; i < num; i++)
+       {
+               AclItem    *aidata = &aidat[i];
+
+               if (aidata->ai_grantee == ACL_ID_PUBLIC ||
+                       aidata->ai_grantee == roleid)
+               {
+                       result |= aidata->ai_privs & mask;
+                       if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
+                               return result;
+               }
+       }
+
+       /*
+        * Check privileges granted indirectly via role memberships. We do this in
+        * a separate pass to minimize expensive indirect membership tests.  In
+        * particular, it's worth testing whether a given ACL entry grants any
+        * privileges still of interest before we perform the has_privs_of_role
+        * test.
+        */
+       remaining = mask & ~result;
+       for (i = 0; i < num; i++)
+       {
+               AclItem    *aidata = &aidat[i];
+
+               if (aidata->ai_grantee == ACL_ID_PUBLIC ||
+                       aidata->ai_grantee == roleid)
+                       continue;                       /* already checked it */
+
+               if ((aidata->ai_privs & remaining) &&
+                       has_privs_of_role(roleid, aidata->ai_grantee))
+               {
+                       result |= aidata->ai_privs & mask;
+                       if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
+                               return result;
+                       remaining = mask & ~result;
+               }
+       }
+
+       return result;
+}
+
+
+/*
+ * aclmask_direct --- compute bitmask of all privileges held by roleid.
+ *
+ * This is exactly like aclmask() except that we consider only privileges
+ * held *directly* by roleid, not those inherited via role membership.
+ */
+static AclMode
+aclmask_direct(const Acl *acl, Oid roleid, Oid ownerId,
+                          AclMode mask, AclMaskHow how)
+{
+       AclMode         result;
+       AclItem    *aidat;
+       int                     i,
+                               num;
+
+       /*
+        * Null ACL should not happen, since caller should have inserted
+        * appropriate default
+        */
+       if (acl == NULL)
+               elog(ERROR, "null ACL");
+
+       check_acl(acl);
+
+       /* Quick exit for mask == 0 */
+       if (mask == 0)
+               return 0;
+
+       result = 0;
+
+       /* Owner always implicitly has all grant options */
+       if ((mask & ACLITEM_ALL_GOPTION_BITS) &&
+               roleid == ownerId)
+       {
+               result = mask & ACLITEM_ALL_GOPTION_BITS;
+               if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
+                       return result;
+       }
+
+       num = ACL_NUM(acl);
+       aidat = ACL_DAT(acl);
+
+       /*
+        * Check privileges granted directly to roleid (and not to public)
+        */
+       for (i = 0; i < num; i++)
+       {
+               AclItem    *aidata = &aidat[i];
+
+               if (aidata->ai_grantee == roleid)
+               {
+                       result |= aidata->ai_privs & mask;
+                       if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
+                               return result;
+               }
+       }
+
+       return result;
+}
+
+
+/*
+ * aclmembers
+ *             Find out all the roleids mentioned in an Acl.
+ *             Note that we do not distinguish grantors from grantees.
+ *
+ * *roleids is set to point to a palloc'd array containing distinct OIDs
+ * in sorted order.  The length of the array is the function result.
+ */
+int
+aclmembers(const Acl *acl, Oid **roleids)
+{
+       Oid                *list;
+       const AclItem *acldat;
+       int                     i,
+                               j,
+                               k;
+
+       if (acl == NULL || ACL_NUM(acl) == 0)
+       {
+               *roleids = NULL;
+               return 0;
+       }
+
+       check_acl(acl);
+
+       /* Allocate the worst-case space requirement */
+       list = palloc(ACL_NUM(acl) * 2 * sizeof(Oid));
+       acldat = ACL_DAT(acl);
+
+       /*
+        * Walk the ACL collecting mentioned RoleIds.
+        */
+       j = 0;
+       for (i = 0; i < ACL_NUM(acl); i++)
+       {
+               const AclItem *ai = &acldat[i];
+
+               if (ai->ai_grantee != ACL_ID_PUBLIC)
+                       list[j++] = ai->ai_grantee;
+               /* grantor is currently never PUBLIC, but let's check anyway */
+               if (ai->ai_grantor != ACL_ID_PUBLIC)
+                       list[j++] = ai->ai_grantor;
+       }
+
+       /* Sort the array */
+       qsort(list, j, sizeof(Oid), oidComparator);
+
+       /* Remove duplicates from the array */
+       k = 0;
+       for (i = 1; i < j; i++)
+       {
+               if (list[k] != list[i])
+                       list[++k] = list[i];
+       }
+
+       /*
+        * We could repalloc the array down to minimum size, but it's hardly worth
+        * it since it's only transient memory.
+        */
+       *roleids = list;
+
+       return k + 1;
+}
+
+/*
+ * oidComparator
+ *             qsort comparison function for Oids
+ */
+static int
+oidComparator(const void *arg1, const void *arg2)
+{
+       Oid                     oid1 = *(const Oid *) arg1;
+       Oid                     oid2 = *(const Oid *) arg2;
+
+       if (oid1 > oid2)
+               return 1;
+       if (oid1 < oid2)
+               return -1;
+       return 0;
+}
+
+
+/*
+ * aclinsert (exported function)
+ */
+Datum
+aclinsert(PG_FUNCTION_ARGS)
+{
+       ereport(ERROR,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("aclinsert is no longer supported")));
+
+       PG_RETURN_NULL();                       /* keep compiler quiet */
+}
+
+Datum
+aclremove(PG_FUNCTION_ARGS)
+{
+       ereport(ERROR,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("aclremove is no longer supported")));
+
+       PG_RETURN_NULL();                       /* keep compiler quiet */
+}
+
+Datum
+aclcontains(PG_FUNCTION_ARGS)
+{
+       Acl                *acl = PG_GETARG_ACL_P(0);
+       AclItem    *aip = PG_GETARG_ACLITEM_P(1);
+       AclItem    *aidat;
+       int                     i,
+                               num;
+
+       check_acl(acl);
+       num = ACL_NUM(acl);
+       aidat = ACL_DAT(acl);
+       for (i = 0; i < num; ++i)
+       {
+               if (aip->ai_grantee == aidat[i].ai_grantee &&
+                       aip->ai_grantor == aidat[i].ai_grantor &&
+                       (ACLITEM_GET_RIGHTS(*aip) & ACLITEM_GET_RIGHTS(aidat[i])) == ACLITEM_GET_RIGHTS(*aip))
+                       PG_RETURN_BOOL(true);
+       }
+       PG_RETURN_BOOL(false);
+}
+
+Datum
+makeaclitem(PG_FUNCTION_ARGS)
+{
+       Oid                     grantee = PG_GETARG_OID(0);
+       Oid                     grantor = PG_GETARG_OID(1);
+       text       *privtext = PG_GETARG_TEXT_P(2);
+       bool            goption = PG_GETARG_BOOL(3);
+       AclItem    *result;
+       AclMode         priv;
+
+       priv = convert_priv_string(privtext);
+
+       result = (AclItem *) palloc(sizeof(AclItem));
+
+       result->ai_grantee = grantee;
+       result->ai_grantor = grantor;
+
+       ACLITEM_SET_PRIVS_GOPTIONS(*result, priv,
+                                                          (goption ? priv : ACL_NO_RIGHTS));
+
+       PG_RETURN_ACLITEM_P(result);
+}
+
+static AclMode
+convert_priv_string(text *priv_type_text)
+{
+       char       *priv_type = text_to_cstring(priv_type_text);
+
+       if (pg_strcasecmp(priv_type, "SELECT") == 0)
+               return ACL_SELECT;
+       if (pg_strcasecmp(priv_type, "INSERT") == 0)
+               return ACL_INSERT;
+       if (pg_strcasecmp(priv_type, "UPDATE") == 0)
+               return ACL_UPDATE;
+       if (pg_strcasecmp(priv_type, "DELETE") == 0)
+               return ACL_DELETE;
+       if (pg_strcasecmp(priv_type, "TRUNCATE") == 0)
+               return ACL_TRUNCATE;
+       if (pg_strcasecmp(priv_type, "REFERENCES") == 0)
+               return ACL_REFERENCES;
+       if (pg_strcasecmp(priv_type, "TRIGGER") == 0)
+               return ACL_TRIGGER;
+       if (pg_strcasecmp(priv_type, "EXECUTE") == 0)
+               return ACL_EXECUTE;
+       if (pg_strcasecmp(priv_type, "USAGE") == 0)
+               return ACL_USAGE;
+       if (pg_strcasecmp(priv_type, "CREATE") == 0)
+               return ACL_CREATE;
+       if (pg_strcasecmp(priv_type, "TEMP") == 0)
+               return ACL_CREATE_TEMP;
+       if (pg_strcasecmp(priv_type, "TEMPORARY") == 0)
+               return ACL_CREATE_TEMP;
+       if (pg_strcasecmp(priv_type, "CONNECT") == 0)
+               return ACL_CONNECT;
+       if (pg_strcasecmp(priv_type, "RULE") == 0)
+               return 0;                               /* ignore old RULE privileges */
+
+       ereport(ERROR,
+                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("unrecognized privilege type: \"%s\"", priv_type)));
+       return ACL_NO_RIGHTS;           /* keep compiler quiet */
+}
+
+
+/*
+ * convert_any_priv_string: recognize privilege strings for has_foo_privilege
+ *
+ * We accept a comma-separated list of case-insensitive privilege names,
+ * producing a bitmask of the OR'd privilege bits.  We are liberal about
+ * whitespace between items, not so much about whitespace within items.
+ * The allowed privilege names are given as an array of priv_map structs,
+ * terminated by one with a NULL name pointer.
+ */
+static AclMode
+convert_any_priv_string(text *priv_type_text,
+                                               const priv_map *privileges)
+{
+       AclMode         result = 0;
+       char       *priv_type = text_to_cstring(priv_type_text);
+       char       *chunk;
+       char       *next_chunk;
+
+       /* We rely on priv_type being a private, modifiable string */
+       for (chunk = priv_type; chunk; chunk = next_chunk)
+       {
+               int                     chunk_len;
+               const priv_map *this_priv;
+
+               /* Split string at commas */
+               next_chunk = strchr(chunk, ',');
+               if (next_chunk)
+                       *next_chunk++ = '\0';
+
+               /* Drop leading/trailing whitespace in this chunk */
+               while (*chunk && isspace((unsigned char) *chunk))
+                       chunk++;
+               chunk_len = strlen(chunk);
+               while (chunk_len > 0 && isspace((unsigned char) chunk[chunk_len - 1]))
+                       chunk_len--;
+               chunk[chunk_len] = '\0';
+
+               /* Match to the privileges list */
+               for (this_priv = privileges; this_priv->name; this_priv++)
+               {
+                       if (pg_strcasecmp(this_priv->name, chunk) == 0)
+                       {
+                               result |= this_priv->value;
+                               break;
+                       }
                }
+               if (!this_priv->name)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("unrecognized privilege type: \"%s\"", chunk)));
        }
 
-       PG_RETURN_ACL_P(new_acl);
+       pfree(priv_type);
+       return result;
+}
+
+
+static const char *
+convert_aclright_to_string(int aclright)
+{
+       switch (aclright)
+       {
+               case ACL_INSERT:
+                       return "INSERT";
+               case ACL_SELECT:
+                       return "SELECT";
+               case ACL_UPDATE:
+                       return "UPDATE";
+               case ACL_DELETE:
+                       return "DELETE";
+               case ACL_TRUNCATE:
+                       return "TRUNCATE";
+               case ACL_REFERENCES:
+                       return "REFERENCES";
+               case ACL_TRIGGER:
+                       return "TRIGGER";
+               case ACL_EXECUTE:
+                       return "EXECUTE";
+               case ACL_USAGE:
+                       return "USAGE";
+               case ACL_CREATE:
+                       return "CREATE";
+               case ACL_CREATE_TEMP:
+                       return "TEMPORARY";
+               case ACL_CONNECT:
+                       return "CONNECT";
+               default:
+                       elog(ERROR, "unrecognized aclright: %d", aclright);
+                       return NULL;
+       }
+}
+
+
+/*----------
+ * Convert an aclitem[] to a table.
+ *
+ * Example:
+ *
+ * aclexplode('{=r/joe,foo=a*w/joe}'::aclitem[])
+ *
+ * returns the table
+ *
+ * {{ OID(joe), 0::OID,   'SELECT', false },
+ *  { OID(joe), OID(foo), 'INSERT', true },
+ *  { OID(joe), OID(foo), 'UPDATE', false }}
+ *----------
+ */
+Datum
+aclexplode(PG_FUNCTION_ARGS)
+{
+       FuncCallContext *funcctx;
+       int                *idx;
+       Acl                *acl = PG_GETARG_ACL_P(0);
+       AclItem    *aidat;
+
+       if (SRF_IS_FIRSTCALL())
+       {
+               TupleDesc       tupdesc;
+               MemoryContext oldcontext;
+
+               check_acl(acl);
+
+               funcctx = SRF_FIRSTCALL_INIT();
+               oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+               /*
+                * build tupdesc for result tuples (matches out parameters in
+                * pg_proc entry)
+                */
+               tupdesc = CreateTemplateTupleDesc(4, false);
+               TupleDescInitEntry(tupdesc, (AttrNumber) 1, "grantor",
+                                                  OIDOID, -1, 0);
+               TupleDescInitEntry(tupdesc, (AttrNumber) 2, "grantee",
+                                                  OIDOID, -1, 0);
+               TupleDescInitEntry(tupdesc, (AttrNumber) 3, "privilege_type",
+                                                  TEXTOID, -1, 0);
+               TupleDescInitEntry(tupdesc, (AttrNumber) 4, "is_grantable",
+                                                  BOOLOID, -1, 0);
+
+               funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+
+               /* allocate memory for user context */
+               idx = (int *) palloc(sizeof(int[2]));
+               idx[0] = 0;                             /* ACL array item index */
+               idx[1] = -1;                    /* privilege type counter */
+               funcctx->user_fctx = (void *) idx;
+
+               MemoryContextSwitchTo(oldcontext);
+       }
+
+       funcctx = SRF_PERCALL_SETUP();
+       idx = (int *) funcctx->user_fctx;
+
+       aidat = ACL_DAT(acl);
+       while (1)
+       {
+               idx[1]++;
+               if (idx[1] == N_ACL_RIGHTS)
+               {
+                       idx[1] = 0;
+                       idx[0]++;
+                       if (idx[0] == ACL_NUM(acl))
+                               /* done */
+                               break;
+               }
+
+               Assert(idx[0] < ACL_NUM(acl));
+               Assert(idx[1] < N_ACL_RIGHTS);
+
+               if (ACLITEM_GET_PRIVS(aidat[idx[0]]) & (1 << idx[1]))
+               {
+                       Datum           result;
+                       Datum           values[4];
+                       bool            nulls[4];
+                       HeapTuple       tuple;
+
+                       values[0] = ObjectIdGetDatum(aidat[idx[0]].ai_grantor);
+                       values[1] = ObjectIdGetDatum(aidat[idx[0]].ai_grantee);
+                       values[2] = CStringGetTextDatum(convert_aclright_to_string(1 << idx[1]));
+                       values[3] = BoolGetDatum(ACLITEM_GET_GOPTIONS(aidat[idx[0]]) & (1 << idx[1]));
+
+                       MemSet(nulls, 0, sizeof(nulls));
+
+                       tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
+                       result = HeapTupleGetDatum(tuple);
+
+                       SRF_RETURN_NEXT(funcctx, result);
+               }
+       }
+
+       SRF_RETURN_DONE(funcctx);
+}
+
+
+/*
+ * has_table_privilege variants
+ *             These are all named "has_table_privilege" at the SQL level.
+ *             They take various combinations of relation name, relation OID,
+ *             user name, user OID, or implicit user = current_user.
+ *
+ *             The result is a boolean value: true if user has the indicated
+ *             privilege, false if not.  The variants that take a relation OID
+ *             return NULL if the OID doesn't exist (rather than failing, as
+ *             they did before Postgres 8.4).
+ */
+
+/*
+ * has_table_privilege_name_name
+ *             Check user privileges on a table given
+ *             name username, text tablename, and text priv name.
+ */
+Datum
+has_table_privilege_name_name(PG_FUNCTION_ARGS)
+{
+       Name            rolename = PG_GETARG_NAME(0);
+       text       *tablename = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       Oid                     tableoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = get_roleid_checked(NameStr(*rolename));
+       tableoid = convert_table_name(tablename);
+       mode = convert_table_priv_string(priv_type_text);
+
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_table_privilege_name
+ *             Check user privileges on a table given
+ *             text tablename and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_table_privilege_name(PG_FUNCTION_ARGS)
+{
+       text       *tablename = PG_GETARG_TEXT_P(0);
+       text       *priv_type_text = PG_GETARG_TEXT_P(1);
+       Oid                     roleid;
+       Oid                     tableoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = GetUserId();
+       tableoid = convert_table_name(tablename);
+       mode = convert_table_priv_string(priv_type_text);
+
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_table_privilege_name_id
+ *             Check user privileges on a table given
+ *             name usename, table oid, and text priv name.
+ */
+Datum
+has_table_privilege_name_id(PG_FUNCTION_ARGS)
+{
+       Name            username = PG_GETARG_NAME(0);
+       Oid                     tableoid = PG_GETARG_OID(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = get_roleid_checked(NameStr(*username));
+       mode = convert_table_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(RELOID,
+                                                         ObjectIdGetDatum(tableoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_table_privilege_id
+ *             Check user privileges on a table given
+ *             table oid, and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_table_privilege_id(PG_FUNCTION_ARGS)
+{
+       Oid                     tableoid = PG_GETARG_OID(0);
+       text       *priv_type_text = PG_GETARG_TEXT_P(1);
+       Oid                     roleid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = GetUserId();
+       mode = convert_table_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(RELOID,
+                                                         ObjectIdGetDatum(tableoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_table_privilege_id_name
+ *             Check user privileges on a table given
+ *             roleid, text tablename, and text priv name.
+ */
+Datum
+has_table_privilege_id_name(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       text       *tablename = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     tableoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       tableoid = convert_table_name(tablename);
+       mode = convert_table_priv_string(priv_type_text);
+
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_table_privilege_id_id
+ *             Check user privileges on a table given
+ *             roleid, table oid, and text priv name.
+ */
+Datum
+has_table_privilege_id_id(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     tableoid = PG_GETARG_OID(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       AclMode         mode;
+       AclResult       aclresult;
+
+       mode = convert_table_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(RELOID,
+                                                         ObjectIdGetDatum(tableoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ *             Support routines for has_table_privilege family.
+ */
+
+/*
+ * Given a table name expressed as a string, look it up and return Oid
+ */
+static Oid
+convert_table_name(text *tablename)
+{
+       RangeVar   *relrv;
+
+       relrv = makeRangeVarFromNameList(textToQualifiedNameList(tablename));
+
+       return RangeVarGetRelid(relrv, false);
+}
+
+/*
+ * convert_table_priv_string
+ *             Convert text string to AclMode value.
+ */
+static AclMode
+convert_table_priv_string(text *priv_type_text)
+{
+       static const priv_map table_priv_map[] = {
+               {"SELECT", ACL_SELECT},
+               {"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)},
+               {"INSERT", ACL_INSERT},
+               {"INSERT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_INSERT)},
+               {"UPDATE", ACL_UPDATE},
+               {"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)},
+               {"DELETE", ACL_DELETE},
+               {"DELETE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_DELETE)},
+               {"TRUNCATE", ACL_TRUNCATE},
+               {"TRUNCATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRUNCATE)},
+               {"REFERENCES", ACL_REFERENCES},
+               {"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)},
+               {"TRIGGER", ACL_TRIGGER},
+               {"TRIGGER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRIGGER)},
+               {"RULE", 0},                    /* ignore old RULE privileges */
+               {"RULE WITH GRANT OPTION", 0},
+               {NULL, 0}
+       };
+
+       return convert_any_priv_string(priv_type_text, table_priv_map);
+}
+
+/*
+ * has_sequence_privilege variants
+ *             These are all named "has_sequence_privilege" at the SQL level.
+ *             They take various combinations of relation name, relation OID,
+ *             user name, user OID, or implicit user = current_user.
+ *
+ *             The result is a boolean value: true if user has the indicated
+ *             privilege, false if not.  The variants that take a relation OID
+ *             return NULL if the OID doesn't exist.
+ */
+
+/*
+ * has_sequence_privilege_name_name
+ *             Check user privileges on a sequence given
+ *             name username, text sequencename, and text priv name.
+ */
+Datum
+has_sequence_privilege_name_name(PG_FUNCTION_ARGS)
+{
+       Name            rolename = PG_GETARG_NAME(0);
+       text       *sequencename = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       Oid                     sequenceoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = get_roleid_checked(NameStr(*rolename));
+       mode = convert_sequence_priv_string(priv_type_text);
+       sequenceoid = convert_table_name(sequencename);
+       if (get_rel_relkind(sequenceoid) != RELKIND_SEQUENCE)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                               errmsg("\"%s\" is not a sequence",
+                               text_to_cstring(sequencename))));
+
+       aclresult = pg_class_aclcheck(sequenceoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_sequence_privilege_name
+ *             Check user privileges on a sequence given
+ *             text sequencename and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_sequence_privilege_name(PG_FUNCTION_ARGS)
+{
+       text       *sequencename = PG_GETARG_TEXT_P(0);
+       text       *priv_type_text = PG_GETARG_TEXT_P(1);
+       Oid                     roleid;
+       Oid                     sequenceoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = GetUserId();
+       mode = convert_sequence_priv_string(priv_type_text);
+       sequenceoid = convert_table_name(sequencename);
+       if (get_rel_relkind(sequenceoid) != RELKIND_SEQUENCE)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                               errmsg("\"%s\" is not a sequence",
+                               text_to_cstring(sequencename))));
+
+       aclresult = pg_class_aclcheck(sequenceoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_sequence_privilege_name_id
+ *             Check user privileges on a sequence given
+ *             name usename, sequence oid, and text priv name.
+ */
+Datum
+has_sequence_privilege_name_id(PG_FUNCTION_ARGS)
+{
+       Name            username = PG_GETARG_NAME(0);
+       Oid                     sequenceoid = PG_GETARG_OID(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       AclMode         mode;
+       AclResult       aclresult;
+       char            relkind;
+
+       roleid = get_roleid_checked(NameStr(*username));
+       mode = convert_sequence_priv_string(priv_type_text);
+       relkind = get_rel_relkind(sequenceoid);
+       if (relkind == '\0')
+               PG_RETURN_NULL();
+       else if (relkind != RELKIND_SEQUENCE)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                               errmsg("\"%s\" is not a sequence",
+                               get_rel_name(sequenceoid))));
+
+       aclresult = pg_class_aclcheck(sequenceoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_sequence_privilege_id
+ *             Check user privileges on a sequence given
+ *             sequence oid, and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_sequence_privilege_id(PG_FUNCTION_ARGS)
+{
+       Oid                     sequenceoid = PG_GETARG_OID(0);
+       text       *priv_type_text = PG_GETARG_TEXT_P(1);
+       Oid                     roleid;
+       AclMode         mode;
+       AclResult       aclresult;
+       char            relkind;
+
+       roleid = GetUserId();
+       mode = convert_sequence_priv_string(priv_type_text);
+       relkind = get_rel_relkind(sequenceoid);
+       if (relkind == '\0')
+               PG_RETURN_NULL();
+       else if (relkind != RELKIND_SEQUENCE)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                               errmsg("\"%s\" is not a sequence",
+                               get_rel_name(sequenceoid))));
+
+       aclresult = pg_class_aclcheck(sequenceoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_sequence_privilege_id_name
+ *             Check user privileges on a sequence given
+ *             roleid, text sequencename, and text priv name.
+ */
+Datum
+has_sequence_privilege_id_name(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       text       *sequencename = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     sequenceoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       mode = convert_sequence_priv_string(priv_type_text);
+       sequenceoid = convert_table_name(sequencename);
+       if (get_rel_relkind(sequenceoid) != RELKIND_SEQUENCE)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                               errmsg("\"%s\" is not a sequence",
+                               text_to_cstring(sequencename))));
+
+       aclresult = pg_class_aclcheck(sequenceoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_sequence_privilege_id_id
+ *             Check user privileges on a sequence given
+ *             roleid, sequence oid, and text priv name.
+ */
+Datum
+has_sequence_privilege_id_id(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     sequenceoid = PG_GETARG_OID(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       AclMode         mode;
+       AclResult       aclresult;
+       char            relkind;
+
+       mode = convert_sequence_priv_string(priv_type_text);
+       relkind = get_rel_relkind(sequenceoid);
+       if (relkind == '\0')
+               PG_RETURN_NULL();
+       else if (relkind != RELKIND_SEQUENCE)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                               errmsg("\"%s\" is not a sequence",
+                               get_rel_name(sequenceoid))));
+
+       aclresult = pg_class_aclcheck(sequenceoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * convert_sequence_priv_string
+ *             Convert text string to AclMode value.
+ */
+static AclMode
+convert_sequence_priv_string(text *priv_type_text)
+{
+       static const priv_map sequence_priv_map[] = {
+               { "USAGE", ACL_USAGE },
+               { "SELECT", ACL_SELECT },
+               { "UPDATE", ACL_UPDATE },
+               { NULL, 0 }
+       };
+
+       return convert_any_priv_string(priv_type_text, sequence_priv_map);
+}
+
+
+/*
+ * has_any_column_privilege variants
+ *             These are all named "has_any_column_privilege" at the SQL level.
+ *             They take various combinations of relation name, relation OID,
+ *             user name, user OID, or implicit user = current_user.
+ *
+ *             The result is a boolean value: true if user has the indicated
+ *             privilege for any column of the table, false if not.  The variants
+ *             that take a relation OID return NULL if the OID doesn't exist.
+ */
+
+/*
+ * has_any_column_privilege_name_name
+ *             Check user privileges on any column of a table given
+ *             name username, text tablename, and text priv name.
+ */
+Datum
+has_any_column_privilege_name_name(PG_FUNCTION_ARGS)
+{
+       Name            rolename = PG_GETARG_NAME(0);
+       text       *tablename = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       Oid                     tableoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = get_roleid_checked(NameStr(*rolename));
+       tableoid = convert_table_name(tablename);
+       mode = convert_column_priv_string(priv_type_text);
+
+       /* First check at table level, then examine each column if needed */
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+       if (aclresult != ACLCHECK_OK)
+               aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode,
+                                                                                         ACLMASK_ANY);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_any_column_privilege_name
+ *             Check user privileges on any column of a table given
+ *             text tablename and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_any_column_privilege_name(PG_FUNCTION_ARGS)
+{
+       text       *tablename = PG_GETARG_TEXT_P(0);
+       text       *priv_type_text = PG_GETARG_TEXT_P(1);
+       Oid                     roleid;
+       Oid                     tableoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = GetUserId();
+       tableoid = convert_table_name(tablename);
+       mode = convert_column_priv_string(priv_type_text);
+
+       /* First check at table level, then examine each column if needed */
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+       if (aclresult != ACLCHECK_OK)
+               aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode,
+                                                                                         ACLMASK_ANY);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_any_column_privilege_name_id
+ *             Check user privileges on any column of a table given
+ *             name usename, table oid, and text priv name.
+ */
+Datum
+has_any_column_privilege_name_id(PG_FUNCTION_ARGS)
+{
+       Name            username = PG_GETARG_NAME(0);
+       Oid                     tableoid = PG_GETARG_OID(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = get_roleid_checked(NameStr(*username));
+       mode = convert_column_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(RELOID,
+                                                         ObjectIdGetDatum(tableoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       /* First check at table level, then examine each column if needed */
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+       if (aclresult != ACLCHECK_OK)
+               aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode,
+                                                                                         ACLMASK_ANY);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_any_column_privilege_id
+ *             Check user privileges on any column of a table given
+ *             table oid, and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_any_column_privilege_id(PG_FUNCTION_ARGS)
+{
+       Oid                     tableoid = PG_GETARG_OID(0);
+       text       *priv_type_text = PG_GETARG_TEXT_P(1);
+       Oid                     roleid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = GetUserId();
+       mode = convert_column_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(RELOID,
+                                                         ObjectIdGetDatum(tableoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       /* First check at table level, then examine each column if needed */
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+       if (aclresult != ACLCHECK_OK)
+               aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode,
+                                                                                         ACLMASK_ANY);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_any_column_privilege_id_name
+ *             Check user privileges on any column of a table given
+ *             roleid, text tablename, and text priv name.
+ */
+Datum
+has_any_column_privilege_id_name(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       text       *tablename = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     tableoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       tableoid = convert_table_name(tablename);
+       mode = convert_column_priv_string(priv_type_text);
+
+       /* First check at table level, then examine each column if needed */
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+       if (aclresult != ACLCHECK_OK)
+               aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode,
+                                                                                         ACLMASK_ANY);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_any_column_privilege_id_id
+ *             Check user privileges on any column of a table given
+ *             roleid, table oid, and text priv name.
+ */
+Datum
+has_any_column_privilege_id_id(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     tableoid = PG_GETARG_OID(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       AclMode         mode;
+       AclResult       aclresult;
+
+       mode = convert_column_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(RELOID,
+                                                         ObjectIdGetDatum(tableoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       /* First check at table level, then examine each column if needed */
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+       if (aclresult != ACLCHECK_OK)
+               aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode,
+                                                                                         ACLMASK_ANY);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+
+/*
+ * has_column_privilege variants
+ *             These are all named "has_column_privilege" at the SQL level.
+ *             They take various combinations of relation name, relation OID,
+ *             column name, column attnum, user name, user OID, or
+ *             implicit user = current_user.
+ *
+ *             The result is a boolean value: true if user has the indicated
+ *             privilege, false if not.  The variants that take a relation OID
+ *             and an integer attnum return NULL (rather than throwing an error)
+ *             if the column doesn't exist or is dropped.
+ */
+
+/*
+ * column_privilege_check: check column privileges, but don't throw an error
+ *             for dropped column or table
+ *
+ * Returns 1 if have the privilege, 0 if not, -1 if dropped column/table.
+ */
+static int
+column_privilege_check(Oid tableoid, AttrNumber attnum,
+                                          Oid roleid, AclMode mode)
+{
+       AclResult       aclresult;
+       HeapTuple       attTuple;
+       Form_pg_attribute attributeForm;
+
+       /*
+        * First check if we have the privilege at the table level.  We check
+        * existence of the pg_class row before risking calling pg_class_aclcheck.
+        * Note: it might seem there's a race condition against concurrent DROP,
+        * but really it's safe because there will be no syscache flush between
+        * here and there.      So if we see the row in the syscache, so will
+        * pg_class_aclcheck.
+        */
+       if (!SearchSysCacheExists(RELOID,
+                                                         ObjectIdGetDatum(tableoid),
+                                                         0, 0, 0))
+               return -1;
+
+       aclresult = pg_class_aclcheck(tableoid, roleid, mode);
+
+       if (aclresult == ACLCHECK_OK)
+               return true;
+
+       /*
+        * No table privilege, so try per-column privileges.  Again, we have to
+        * check for dropped attribute first, and we rely on the syscache not to
+        * notice a concurrent drop before pg_attribute_aclcheck fetches the row.
+        */
+       attTuple = SearchSysCache(ATTNUM,
+                                                         ObjectIdGetDatum(tableoid),
+                                                         Int16GetDatum(attnum),
+                                                         0, 0);
+       if (!HeapTupleIsValid(attTuple))
+               return -1;
+       attributeForm = (Form_pg_attribute) GETSTRUCT(attTuple);
+       if (attributeForm->attisdropped)
+       {
+               ReleaseSysCache(attTuple);
+               return -1;
+       }
+       ReleaseSysCache(attTuple);
+
+       aclresult = pg_attribute_aclcheck(tableoid, attnum, roleid, mode);
+
+       return (aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_column_privilege_name_name_name
+ *             Check user privileges on a column given
+ *             name username, text tablename, text colname, and text priv name.
+ */
+Datum
+has_column_privilege_name_name_name(PG_FUNCTION_ARGS)
+{
+       Name            rolename = PG_GETARG_NAME(0);
+       text       *tablename = PG_GETARG_TEXT_P(1);
+       text       *column = PG_GETARG_TEXT_P(2);
+       text       *priv_type_text = PG_GETARG_TEXT_P(3);
+       Oid                     roleid;
+       Oid                     tableoid;
+       AttrNumber      colattnum;
+       AclMode         mode;
+       int                     privresult;
+
+       roleid = get_roleid_checked(NameStr(*rolename));
+       tableoid = convert_table_name(tablename);
+       colattnum = convert_column_name(tableoid, column);
+       mode = convert_column_priv_string(priv_type_text);
+
+       privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+       if (privresult < 0)
+               PG_RETURN_NULL();
+       PG_RETURN_BOOL(privresult);
+}
+
+/*
+ * has_column_privilege_name_name_attnum
+ *             Check user privileges on a column given
+ *             name username, text tablename, int attnum, and text priv name.
+ */
+Datum
+has_column_privilege_name_name_attnum(PG_FUNCTION_ARGS)
+{
+       Name            rolename = PG_GETARG_NAME(0);
+       text       *tablename = PG_GETARG_TEXT_P(1);
+       AttrNumber      colattnum = PG_GETARG_INT16(2);
+       text       *priv_type_text = PG_GETARG_TEXT_P(3);
+       Oid                     roleid;
+       Oid                     tableoid;
+       AclMode         mode;
+       int                     privresult;
+
+       roleid = get_roleid_checked(NameStr(*rolename));
+       tableoid = convert_table_name(tablename);
+       mode = convert_column_priv_string(priv_type_text);
+
+       privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+       if (privresult < 0)
+               PG_RETURN_NULL();
+       PG_RETURN_BOOL(privresult);
+}
+
+/*
+ * has_column_privilege_name_id_name
+ *             Check user privileges on a column given
+ *             name username, table oid, text colname, and text priv name.
+ */
+Datum
+has_column_privilege_name_id_name(PG_FUNCTION_ARGS)
+{
+       Name            username = PG_GETARG_NAME(0);
+       Oid                     tableoid = PG_GETARG_OID(1);
+       text       *column = PG_GETARG_TEXT_P(2);
+       text       *priv_type_text = PG_GETARG_TEXT_P(3);
+       Oid                     roleid;
+       AttrNumber      colattnum;
+       AclMode         mode;
+       int                     privresult;
+
+       roleid = get_roleid_checked(NameStr(*username));
+       colattnum = convert_column_name(tableoid, column);
+       mode = convert_column_priv_string(priv_type_text);
+
+       privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+       if (privresult < 0)
+               PG_RETURN_NULL();
+       PG_RETURN_BOOL(privresult);
+}
+
+/*
+ * has_column_privilege_name_id_attnum
+ *             Check user privileges on a column given
+ *             name username, table oid, int attnum, and text priv name.
+ */
+Datum
+has_column_privilege_name_id_attnum(PG_FUNCTION_ARGS)
+{
+       Name            username = PG_GETARG_NAME(0);
+       Oid                     tableoid = PG_GETARG_OID(1);
+       AttrNumber      colattnum = PG_GETARG_INT16(2);
+       text       *priv_type_text = PG_GETARG_TEXT_P(3);
+       Oid                     roleid;
+       AclMode         mode;
+       int                     privresult;
+
+       roleid = get_roleid_checked(NameStr(*username));
+       mode = convert_column_priv_string(priv_type_text);
+
+       privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+       if (privresult < 0)
+               PG_RETURN_NULL();
+       PG_RETURN_BOOL(privresult);
+}
+
+/*
+ * has_column_privilege_id_name_name
+ *             Check user privileges on a column given
+ *             oid roleid, text tablename, text colname, and text priv name.
+ */
+Datum
+has_column_privilege_id_name_name(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       text       *tablename = PG_GETARG_TEXT_P(1);
+       text       *column = PG_GETARG_TEXT_P(2);
+       text       *priv_type_text = PG_GETARG_TEXT_P(3);
+       Oid                     tableoid;
+       AttrNumber      colattnum;
+       AclMode         mode;
+       int                     privresult;
+
+       tableoid = convert_table_name(tablename);
+       colattnum = convert_column_name(tableoid, column);
+       mode = convert_column_priv_string(priv_type_text);
+
+       privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+       if (privresult < 0)
+               PG_RETURN_NULL();
+       PG_RETURN_BOOL(privresult);
+}
+
+/*
+ * has_column_privilege_id_name_attnum
+ *             Check user privileges on a column given
+ *             oid roleid, text tablename, int attnum, and text priv name.
+ */
+Datum
+has_column_privilege_id_name_attnum(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       text       *tablename = PG_GETARG_TEXT_P(1);
+       AttrNumber      colattnum = PG_GETARG_INT16(2);
+       text       *priv_type_text = PG_GETARG_TEXT_P(3);
+       Oid                     tableoid;
+       AclMode         mode;
+       int                     privresult;
+
+       tableoid = convert_table_name(tablename);
+       mode = convert_column_priv_string(priv_type_text);
+
+       privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+       if (privresult < 0)
+               PG_RETURN_NULL();
+       PG_RETURN_BOOL(privresult);
+}
+
+/*
+ * has_column_privilege_id_id_name
+ *             Check user privileges on a column given
+ *             oid roleid, table oid, text colname, and text priv name.
+ */
+Datum
+has_column_privilege_id_id_name(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     tableoid = PG_GETARG_OID(1);
+       text       *column = PG_GETARG_TEXT_P(2);
+       text       *priv_type_text = PG_GETARG_TEXT_P(3);
+       AttrNumber      colattnum;
+       AclMode         mode;
+       int                     privresult;
+
+       colattnum = convert_column_name(tableoid, column);
+       mode = convert_column_priv_string(priv_type_text);
+
+       privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+       if (privresult < 0)
+               PG_RETURN_NULL();
+       PG_RETURN_BOOL(privresult);
+}
+
+/*
+ * has_column_privilege_id_id_attnum
+ *             Check user privileges on a column given
+ *             oid roleid, table oid, int attnum, and text priv name.
+ */
+Datum
+has_column_privilege_id_id_attnum(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     tableoid = PG_GETARG_OID(1);
+       AttrNumber      colattnum = PG_GETARG_INT16(2);
+       text       *priv_type_text = PG_GETARG_TEXT_P(3);
+       AclMode         mode;
+       int                     privresult;
+
+       mode = convert_column_priv_string(priv_type_text);
+
+       privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+       if (privresult < 0)
+               PG_RETURN_NULL();
+       PG_RETURN_BOOL(privresult);
+}
+
+/*
+ * has_column_privilege_name_name
+ *             Check user privileges on a column given
+ *             text tablename, text colname, and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_column_privilege_name_name(PG_FUNCTION_ARGS)
+{
+       text       *tablename = PG_GETARG_TEXT_P(0);
+       text       *column = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       Oid                     tableoid;
+       AttrNumber      colattnum;
+       AclMode         mode;
+       int                     privresult;
+
+       roleid = GetUserId();
+       tableoid = convert_table_name(tablename);
+       colattnum = convert_column_name(tableoid, column);
+       mode = convert_column_priv_string(priv_type_text);
+
+       privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+       if (privresult < 0)
+               PG_RETURN_NULL();
+       PG_RETURN_BOOL(privresult);
+}
+
+/*
+ * has_column_privilege_name_attnum
+ *             Check user privileges on a column given
+ *             text tablename, int attnum, and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_column_privilege_name_attnum(PG_FUNCTION_ARGS)
+{
+       text       *tablename = PG_GETARG_TEXT_P(0);
+       AttrNumber      colattnum = PG_GETARG_INT16(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       Oid                     tableoid;
+       AclMode         mode;
+       int                     privresult;
+
+       roleid = GetUserId();
+       tableoid = convert_table_name(tablename);
+       mode = convert_column_priv_string(priv_type_text);
+
+       privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+       if (privresult < 0)
+               PG_RETURN_NULL();
+       PG_RETURN_BOOL(privresult);
+}
+
+/*
+ * has_column_privilege_id_name
+ *             Check user privileges on a column given
+ *             table oid, text colname, and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_column_privilege_id_name(PG_FUNCTION_ARGS)
+{
+       Oid                     tableoid = PG_GETARG_OID(0);
+       text       *column = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       AttrNumber      colattnum;
+       AclMode         mode;
+       int                     privresult;
+
+       roleid = GetUserId();
+       colattnum = convert_column_name(tableoid, column);
+       mode = convert_column_priv_string(priv_type_text);
+
+       privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+       if (privresult < 0)
+               PG_RETURN_NULL();
+       PG_RETURN_BOOL(privresult);
+}
+
+/*
+ * has_column_privilege_id_attnum
+ *             Check user privileges on a column given
+ *             table oid, int attnum, and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_column_privilege_id_attnum(PG_FUNCTION_ARGS)
+{
+       Oid                     tableoid = PG_GETARG_OID(0);
+       AttrNumber      colattnum = PG_GETARG_INT16(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       AclMode         mode;
+       int                     privresult;
+
+       roleid = GetUserId();
+       mode = convert_column_priv_string(priv_type_text);
+
+       privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+       if (privresult < 0)
+               PG_RETURN_NULL();
+       PG_RETURN_BOOL(privresult);
+}
+
+/*
+ *             Support routines for has_column_privilege family.
+ */
+
+/*
+ * Given a table OID and a column name expressed as a string, look it up
+ * and return the column number
+ */
+static AttrNumber
+convert_column_name(Oid tableoid, text *column)
+{
+       AttrNumber      attnum;
+       char       *colname;
+
+       colname = text_to_cstring(column);
+       attnum = get_attnum(tableoid, colname);
+       if (attnum == InvalidAttrNumber)
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                               colname, get_rel_name(tableoid))));
+       pfree(colname);
+       return attnum;
+}
+
+/*
+ * convert_column_priv_string
+ *             Convert text string to AclMode value.
+ */
+static AclMode
+convert_column_priv_string(text *priv_type_text)
+{
+       static const priv_map column_priv_map[] = {
+               {"SELECT", ACL_SELECT},
+               {"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)},
+               {"INSERT", ACL_INSERT},
+               {"INSERT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_INSERT)},
+               {"UPDATE", ACL_UPDATE},
+               {"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)},
+               {"REFERENCES", ACL_REFERENCES},
+               {"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)},
+               {NULL, 0}
+       };
+
+       return convert_any_priv_string(priv_type_text, column_priv_map);
+}
+
+
+/*
+ * has_database_privilege variants
+ *             These are all named "has_database_privilege" at the SQL level.
+ *             They take various combinations of database name, database OID,
+ *             user name, user OID, or implicit user = current_user.
+ *
+ *             The result is a boolean value: true if user has the indicated
+ *             privilege, false if not, or NULL if object doesn't exist.
+ */
+
+/*
+ * has_database_privilege_name_name
+ *             Check user privileges on a database given
+ *             name username, text databasename, and text priv name.
+ */
+Datum
+has_database_privilege_name_name(PG_FUNCTION_ARGS)
+{
+       Name            username = PG_GETARG_NAME(0);
+       text       *databasename = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       Oid                     databaseoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = get_roleid_checked(NameStr(*username));
+       databaseoid = convert_database_name(databasename);
+       mode = convert_database_priv_string(priv_type_text);
+
+       aclresult = pg_database_aclcheck(databaseoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_database_privilege_name
+ *             Check user privileges on a database given
+ *             text databasename and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_database_privilege_name(PG_FUNCTION_ARGS)
+{
+       text       *databasename = PG_GETARG_TEXT_P(0);
+       text       *priv_type_text = PG_GETARG_TEXT_P(1);
+       Oid                     roleid;
+       Oid                     databaseoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = GetUserId();
+       databaseoid = convert_database_name(databasename);
+       mode = convert_database_priv_string(priv_type_text);
+
+       aclresult = pg_database_aclcheck(databaseoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_database_privilege_name_id
+ *             Check user privileges on a database given
+ *             name usename, database oid, and text priv name.
+ */
+Datum
+has_database_privilege_name_id(PG_FUNCTION_ARGS)
+{
+       Name            username = PG_GETARG_NAME(0);
+       Oid                     databaseoid = PG_GETARG_OID(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = get_roleid_checked(NameStr(*username));
+       mode = convert_database_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(DATABASEOID,
+                                                         ObjectIdGetDatum(databaseoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       aclresult = pg_database_aclcheck(databaseoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_database_privilege_id
+ *             Check user privileges on a database given
+ *             database oid, and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_database_privilege_id(PG_FUNCTION_ARGS)
+{
+       Oid                     databaseoid = PG_GETARG_OID(0);
+       text       *priv_type_text = PG_GETARG_TEXT_P(1);
+       Oid                     roleid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = GetUserId();
+       mode = convert_database_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(DATABASEOID,
+                                                         ObjectIdGetDatum(databaseoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       aclresult = pg_database_aclcheck(databaseoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_database_privilege_id_name
+ *             Check user privileges on a database given
+ *             roleid, text databasename, and text priv name.
+ */
+Datum
+has_database_privilege_id_name(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       text       *databasename = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     databaseoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       databaseoid = convert_database_name(databasename);
+       mode = convert_database_priv_string(priv_type_text);
+
+       aclresult = pg_database_aclcheck(databaseoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_database_privilege_id_id
+ *             Check user privileges on a database given
+ *             roleid, database oid, and text priv name.
+ */
+Datum
+has_database_privilege_id_id(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     databaseoid = PG_GETARG_OID(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       AclMode         mode;
+       AclResult       aclresult;
+
+       mode = convert_database_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(DATABASEOID,
+                                                         ObjectIdGetDatum(databaseoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       aclresult = pg_database_aclcheck(databaseoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ *             Support routines for has_database_privilege family.
+ */
+
+/*
+ * Given a database name expressed as a string, look it up and return Oid
+ */
+static Oid
+convert_database_name(text *databasename)
+{
+       char       *dbname = text_to_cstring(databasename);
+       Oid                     oid;
+
+       oid = get_database_oid(dbname);
+       if (!OidIsValid(oid))
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_DATABASE),
+                                errmsg("database \"%s\" does not exist", dbname)));
+
+       return oid;
+}
+
+/*
+ * convert_database_priv_string
+ *             Convert text string to AclMode value.
+ */
+static AclMode
+convert_database_priv_string(text *priv_type_text)
+{
+       static const priv_map database_priv_map[] = {
+               {"CREATE", ACL_CREATE},
+               {"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
+               {"TEMPORARY", ACL_CREATE_TEMP},
+               {"TEMPORARY WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP)},
+               {"TEMP", ACL_CREATE_TEMP},
+               {"TEMP WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP)},
+               {"CONNECT", ACL_CONNECT},
+               {"CONNECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CONNECT)},
+               {NULL, 0}
+       };
+
+       return convert_any_priv_string(priv_type_text, database_priv_map);
+
+}
+
+
+/*
+ * has_foreign_data_wrapper_privilege variants
+ *             These are all named "has_foreign_data_wrapper_privilege" at the SQL level.
+ *             They take various combinations of foreign-data wrapper name,
+ *             fdw OID, user name, user OID, or implicit user = current_user.
+ *
+ *             The result is a boolean value: true if user has the indicated
+ *             privilege, false if not.
+ */
+
+/*
+ * has_foreign_data_wrapper_privilege_name_name
+ *             Check user privileges on a foreign-data wrapper given
+ *             name username, text fdwname, and text priv name.
+ */
+Datum
+has_foreign_data_wrapper_privilege_name_name(PG_FUNCTION_ARGS)
+{
+       Name            username = PG_GETARG_NAME(0);
+       text       *fdwname = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       Oid                     fdwid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = get_roleid_checked(NameStr(*username));
+       fdwid = convert_foreign_data_wrapper_name(fdwname);
+       mode = convert_foreign_data_wrapper_priv_string(priv_type_text);
+
+       aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_foreign_data_wrapper_privilege_name
+ *             Check user privileges on a foreign-data wrapper given
+ *             text fdwname and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_foreign_data_wrapper_privilege_name(PG_FUNCTION_ARGS)
+{
+       text       *fdwname = PG_GETARG_TEXT_P(0);
+       text       *priv_type_text = PG_GETARG_TEXT_P(1);
+       Oid                     roleid;
+       Oid                     fdwid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = GetUserId();
+       fdwid = convert_foreign_data_wrapper_name(fdwname);
+       mode = convert_foreign_data_wrapper_priv_string(priv_type_text);
+
+       aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_foreign_data_wrapper_privilege_name_id
+ *             Check user privileges on a foreign-data wrapper given
+ *             name usename, foreign-data wrapper oid, and text priv name.
+ */
+Datum
+has_foreign_data_wrapper_privilege_name_id(PG_FUNCTION_ARGS)
+{
+       Name            username = PG_GETARG_NAME(0);
+       Oid                     fdwid = PG_GETARG_OID(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = get_roleid_checked(NameStr(*username));
+       mode = convert_foreign_data_wrapper_priv_string(priv_type_text);
+
+       aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_foreign_data_wrapper_privilege_id
+ *             Check user privileges on a foreign-data wrapper given
+ *             foreign-data wrapper oid, and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_foreign_data_wrapper_privilege_id(PG_FUNCTION_ARGS)
+{
+       Oid                     fdwid = PG_GETARG_OID(0);
+       text       *priv_type_text = PG_GETARG_TEXT_P(1);
+       Oid                     roleid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = GetUserId();
+       mode = convert_foreign_data_wrapper_priv_string(priv_type_text);
+
+       aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_foreign_data_wrapper_privilege_id_name
+ *             Check user privileges on a foreign-data wrapper given
+ *             roleid, text fdwname, and text priv name.
+ */
+Datum
+has_foreign_data_wrapper_privilege_id_name(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       text       *fdwname = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     fdwid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       fdwid = convert_foreign_data_wrapper_name(fdwname);
+       mode = convert_foreign_data_wrapper_priv_string(priv_type_text);
+
+       aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_foreign_data_wrapper_privilege_id_id
+ *             Check user privileges on a foreign-data wrapper given
+ *             roleid, fdw oid, and text priv name.
+ */
+Datum
+has_foreign_data_wrapper_privilege_id_id(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     fdwid = PG_GETARG_OID(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       AclMode         mode;
+       AclResult       aclresult;
+
+       mode = convert_foreign_data_wrapper_priv_string(priv_type_text);
+
+       aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ *             Support routines for has_foreign_data_wrapper_privilege family.
+ */
+
+/*
+ * Given a FDW name expressed as a string, look it up and return Oid
+ */
+static Oid
+convert_foreign_data_wrapper_name(text *fdwname)
+{
+       char       *fdwstr = text_to_cstring(fdwname);
+
+       return GetForeignDataWrapperOidByName(fdwstr, false);
+}
+
+/*
+ * convert_foreign_data_wrapper_priv_string
+ *             Convert text string to AclMode value.
+ */
+static AclMode
+convert_foreign_data_wrapper_priv_string(text *priv_type_text)
+{
+       static const priv_map foreign_data_wrapper_priv_map[] = {
+               {"USAGE", ACL_USAGE},
+               {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)},
+               {NULL, 0}
+       };
+
+       return convert_any_priv_string(priv_type_text, foreign_data_wrapper_priv_map);
 }
 
+
+/*
+ * has_function_privilege variants
+ *             These are all named "has_function_privilege" at the SQL level.
+ *             They take various combinations of function name, function OID,
+ *             user name, user OID, or implicit user = current_user.
+ *
+ *             The result is a boolean value: true if user has the indicated
+ *             privilege, false if not, or NULL if object doesn't exist.
+ */
+
+/*
+ * has_function_privilege_name_name
+ *             Check user privileges on a function given
+ *             name username, text functionname, and text priv name.
+ */
 Datum
-aclcontains(PG_FUNCTION_ARGS)
+has_function_privilege_name_name(PG_FUNCTION_ARGS)
 {
-       Acl                *acl = PG_GETARG_ACL_P(0);
-       AclItem    *aip = PG_GETARG_ACLITEM_P(1);
-       AclItem    *aidat;
-       int                     i,
-                               num;
+       Name            username = PG_GETARG_NAME(0);
+       text       *functionname = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       Oid                     functionoid;
+       AclMode         mode;
+       AclResult       aclresult;
 
-       num = ACL_NUM(acl);
-       aidat = ACL_DAT(acl);
-       for (i = 0; i < num; ++i)
-       {
-               if (aip->ai_id == aidat[i].ai_id &&
-                       aip->ai_privs == aidat[i].ai_privs)
-                       PG_RETURN_BOOL(true);
-       }
-       PG_RETURN_BOOL(false);
+       roleid = get_roleid_checked(NameStr(*username));
+       functionoid = convert_function_name(functionname);
+       mode = convert_function_priv_string(priv_type_text);
+
+       aclresult = pg_proc_aclcheck(functionoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_function_privilege_name
+ *             Check user privileges on a function given
+ *             text functionname and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_function_privilege_name(PG_FUNCTION_ARGS)
+{
+       text       *functionname = PG_GETARG_TEXT_P(0);
+       text       *priv_type_text = PG_GETARG_TEXT_P(1);
+       Oid                     roleid;
+       Oid                     functionoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = GetUserId();
+       functionoid = convert_function_name(functionname);
+       mode = convert_function_priv_string(priv_type_text);
+
+       aclresult = pg_proc_aclcheck(functionoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
+/*
+ * has_function_privilege_name_id
+ *             Check user privileges on a function given
+ *             name usename, function oid, and text priv name.
+ */
+Datum
+has_function_privilege_name_id(PG_FUNCTION_ARGS)
+{
+       Name            username = PG_GETARG_NAME(0);
+       Oid                     functionoid = PG_GETARG_OID(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     roleid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = get_roleid_checked(NameStr(*username));
+       mode = convert_function_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(PROCOID,
+                                                         ObjectIdGetDatum(functionoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       aclresult = pg_proc_aclcheck(functionoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
 
 /*
- * has_table_privilege variants
- *             These are all named "has_table_privilege" at the SQL level.
- *             They take various combinations of relation name, relation OID,
- *             user name, user sysid, or implicit user = current_user.
+ * has_function_privilege_id
+ *             Check user privileges on a function given
+ *             function oid, and text priv name.
+ *             current_user is assumed
+ */
+Datum
+has_function_privilege_id(PG_FUNCTION_ARGS)
+{
+       Oid                     functionoid = PG_GETARG_OID(0);
+       text       *priv_type_text = PG_GETARG_TEXT_P(1);
+       Oid                     roleid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       roleid = GetUserId();
+       mode = convert_function_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(PROCOID,
+                                                         ObjectIdGetDatum(functionoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       aclresult = pg_proc_aclcheck(functionoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_function_privilege_id_name
+ *             Check user privileges on a function given
+ *             roleid, text functionname, and text priv name.
+ */
+Datum
+has_function_privilege_id_name(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       text       *functionname = PG_GETARG_TEXT_P(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       Oid                     functionoid;
+       AclMode         mode;
+       AclResult       aclresult;
+
+       functionoid = convert_function_name(functionname);
+       mode = convert_function_priv_string(priv_type_text);
+
+       aclresult = pg_proc_aclcheck(functionoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_function_privilege_id_id
+ *             Check user privileges on a function given
+ *             roleid, function oid, and text priv name.
+ */
+Datum
+has_function_privilege_id_id(PG_FUNCTION_ARGS)
+{
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     functionoid = PG_GETARG_OID(1);
+       text       *priv_type_text = PG_GETARG_TEXT_P(2);
+       AclMode         mode;
+       AclResult       aclresult;
+
+       mode = convert_function_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(PROCOID,
+                                                         ObjectIdGetDatum(functionoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       aclresult = pg_proc_aclcheck(functionoid, roleid, mode);
+
+       PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ *             Support routines for has_function_privilege family.
+ */
+
+/*
+ * Given a function name expressed as a string, look it up and return Oid
+ */
+static Oid
+convert_function_name(text *functionname)
+{
+       char       *funcname = text_to_cstring(functionname);
+       Oid                     oid;
+
+       oid = DatumGetObjectId(DirectFunctionCall1(regprocedurein,
+                                                                                          CStringGetDatum(funcname)));
+
+       if (!OidIsValid(oid))
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                                errmsg("function \"%s\" does not exist", funcname)));
+
+       return oid;
+}
+
+/*
+ * convert_function_priv_string
+ *             Convert text string to AclMode value.
+ */
+static AclMode
+convert_function_priv_string(text *priv_type_text)
+{
+       static const priv_map function_priv_map[] = {
+               {"EXECUTE", ACL_EXECUTE},
+               {"EXECUTE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_EXECUTE)},
+               {NULL, 0}
+       };
+
+       return convert_any_priv_string(priv_type_text, function_priv_map);
+}
+
+
+/*
+ * has_language_privilege variants
+ *             These are all named "has_language_privilege" at the SQL level.
+ *             They take various combinations of language name, language OID,
+ *             user name, user OID, or implicit user = current_user.
  *
  *             The result is a boolean value: true if user has the indicated
- *             privilege, false if not.
+ *             privilege, false if not, or NULL if object doesn't exist.
  */
 
 /*
- * has_table_privilege_name_name
- *             Check user privileges on a table given
- *             name username, text tablename, and text priv name.
+ * has_language_privilege_name_name
+ *             Check user privileges on a language given
+ *             name username, text languagename, and text priv name.
  */
 Datum
-has_table_privilege_name_name(PG_FUNCTION_ARGS)
+has_language_privilege_name_name(PG_FUNCTION_ARGS)
 {
        Name            username = PG_GETARG_NAME(0);
-       text       *tablename = PG_GETARG_TEXT_P(1);
+       text       *languagename = PG_GETARG_TEXT_P(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       int32           usesysid;
-       Oid                     tableoid;
+       Oid                     roleid;
+       Oid                     languageoid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = get_usesysid(NameStr(*username));
-       tableoid = convert_table_name(tablename);
-       mode = convert_table_priv_string(priv_type_text);
+       roleid = get_roleid_checked(NameStr(*username));
+       languageoid = convert_language_name(languagename);
+       mode = convert_language_priv_string(priv_type_text);
 
-       aclresult = pg_class_aclcheck(tableoid, usesysid, mode);
+       aclresult = pg_language_aclcheck(languageoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_table_privilege_name
- *             Check user privileges on a table given
- *             text tablename and text priv name.
+ * has_language_privilege_name
+ *             Check user privileges on a language given
+ *             text languagename and text priv name.
  *             current_user is assumed
  */
 Datum
-has_table_privilege_name(PG_FUNCTION_ARGS)
+has_language_privilege_name(PG_FUNCTION_ARGS)
 {
-       text       *tablename = PG_GETARG_TEXT_P(0);
+       text       *languagename = PG_GETARG_TEXT_P(0);
        text       *priv_type_text = PG_GETARG_TEXT_P(1);
-       int32           usesysid;
-       Oid                     tableoid;
+       Oid                     roleid;
+       Oid                     languageoid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = GetUserId();
-       tableoid = convert_table_name(tablename);
-       mode = convert_table_priv_string(priv_type_text);
+       roleid = GetUserId();
+       languageoid = convert_language_name(languagename);
+       mode = convert_language_priv_string(priv_type_text);
 
-       aclresult = pg_class_aclcheck(tableoid, usesysid, mode);
+       aclresult = pg_language_aclcheck(languageoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_table_privilege_name_id
- *             Check user privileges on a table given
- *             name usename, table oid, and text priv name.
+ * has_language_privilege_name_id
+ *             Check user privileges on a language given
+ *             name usename, language oid, and text priv name.
  */
 Datum
-has_table_privilege_name_id(PG_FUNCTION_ARGS)
+has_language_privilege_name_id(PG_FUNCTION_ARGS)
 {
        Name            username = PG_GETARG_NAME(0);
-       Oid                     tableoid = PG_GETARG_OID(1);
+       Oid                     languageoid = PG_GETARG_OID(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       int32           usesysid;
+       Oid                     roleid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = get_usesysid(NameStr(*username));
-       mode = convert_table_priv_string(priv_type_text);
+       roleid = get_roleid_checked(NameStr(*username));
+       mode = convert_language_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(LANGOID,
+                                                         ObjectIdGetDatum(languageoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
 
-       aclresult = pg_class_aclcheck(tableoid, usesysid, mode);
+       aclresult = pg_language_aclcheck(languageoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_table_privilege_id
- *             Check user privileges on a table given
- *             table oid, and text priv name.
+ * has_language_privilege_id
+ *             Check user privileges on a language given
+ *             language oid, and text priv name.
  *             current_user is assumed
  */
 Datum
-has_table_privilege_id(PG_FUNCTION_ARGS)
+has_language_privilege_id(PG_FUNCTION_ARGS)
 {
-       Oid                     tableoid = PG_GETARG_OID(0);
+       Oid                     languageoid = PG_GETARG_OID(0);
        text       *priv_type_text = PG_GETARG_TEXT_P(1);
-       int32           usesysid;
+       Oid                     roleid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = GetUserId();
-       mode = convert_table_priv_string(priv_type_text);
+       roleid = GetUserId();
+       mode = convert_language_priv_string(priv_type_text);
 
-       aclresult = pg_class_aclcheck(tableoid, usesysid, mode);
+       if (!SearchSysCacheExists(LANGOID,
+                                                         ObjectIdGetDatum(languageoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       aclresult = pg_language_aclcheck(languageoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_table_privilege_id_name
- *             Check user privileges on a table given
- *             usesysid, text tablename, and text priv name.
+ * has_language_privilege_id_name
+ *             Check user privileges on a language given
+ *             roleid, text languagename, and text priv name.
  */
 Datum
-has_table_privilege_id_name(PG_FUNCTION_ARGS)
+has_language_privilege_id_name(PG_FUNCTION_ARGS)
 {
-       int32           usesysid = PG_GETARG_INT32(0);
-       text       *tablename = PG_GETARG_TEXT_P(1);
+       Oid                     roleid = PG_GETARG_OID(0);
+       text       *languagename = PG_GETARG_TEXT_P(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       Oid                     tableoid;
+       Oid                     languageoid;
        AclMode         mode;
        AclResult       aclresult;
 
-       tableoid = convert_table_name(tablename);
-       mode = convert_table_priv_string(priv_type_text);
+       languageoid = convert_language_name(languagename);
+       mode = convert_language_priv_string(priv_type_text);
 
-       aclresult = pg_class_aclcheck(tableoid, usesysid, mode);
+       aclresult = pg_language_aclcheck(languageoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_table_privilege_id_id
- *             Check user privileges on a table given
- *             usesysid, table oid, and text priv name.
+ * has_language_privilege_id_id
+ *             Check user privileges on a language given
+ *             roleid, language oid, and text priv name.
  */
 Datum
-has_table_privilege_id_id(PG_FUNCTION_ARGS)
+has_language_privilege_id_id(PG_FUNCTION_ARGS)
 {
-       int32           usesysid = PG_GETARG_INT32(0);
-       Oid                     tableoid = PG_GETARG_OID(1);
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     languageoid = PG_GETARG_OID(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
        AclMode         mode;
        AclResult       aclresult;
 
-       mode = convert_table_priv_string(priv_type_text);
+       mode = convert_language_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(LANGOID,
+                                                         ObjectIdGetDatum(languageoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
 
-       aclresult = pg_class_aclcheck(tableoid, usesysid, mode);
+       aclresult = pg_language_aclcheck(languageoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- *             Support routines for has_table_privilege family.
+ *             Support routines for has_language_privilege family.
  */
 
 /*
- * Given a table name expressed as a string, look it up and return Oid
+ * Given a language name expressed as a string, look it up and return Oid
  */
 static Oid
-convert_table_name(text *tablename)
+convert_language_name(text *languagename)
 {
-       RangeVar   *relrv;
+       char       *langname = text_to_cstring(languagename);
+       Oid                     oid;
 
-       relrv = makeRangeVarFromNameList(textToQualifiedNameList(tablename,
-                                                                                                       "has_table_privilege"));
+       oid = GetSysCacheOid(LANGNAME,
+                                                CStringGetDatum(langname),
+                                                0, 0, 0);
+       if (!OidIsValid(oid))
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                errmsg("language \"%s\" does not exist", langname)));
 
-       return RangeVarGetRelid(relrv, false);
+       return oid;
 }
 
 /*
- * convert_table_priv_string
+ * convert_language_priv_string
  *             Convert text string to AclMode value.
  */
 static AclMode
-convert_table_priv_string(text *priv_type_text)
+convert_language_priv_string(text *priv_type_text)
 {
-       char       *priv_type;
-
-       priv_type = DatumGetCString(DirectFunctionCall1(textout,
-                                                                                       PointerGetDatum(priv_type_text)));
-
-       /*
-        * Return mode from priv_type string
-        */
-       if (strcasecmp(priv_type, "SELECT") == 0)
-               return ACL_SELECT;
-
-       if (strcasecmp(priv_type, "INSERT") == 0)
-               return ACL_INSERT;
-
-       if (strcasecmp(priv_type, "UPDATE") == 0)
-               return ACL_UPDATE;
-
-       if (strcasecmp(priv_type, "DELETE") == 0)
-               return ACL_DELETE;
-
-       if (strcasecmp(priv_type, "RULE") == 0)
-               return ACL_RULE;
-
-       if (strcasecmp(priv_type, "REFERENCES") == 0)
-               return ACL_REFERENCES;
+       static const priv_map language_priv_map[] = {
+               {"USAGE", ACL_USAGE},
+               {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)},
+               {NULL, 0}
+       };
 
-       if (strcasecmp(priv_type, "TRIGGER") == 0)
-               return ACL_TRIGGER;
-
-       elog(ERROR, "has_table_privilege: invalid privilege type %s",
-                priv_type);
-       return ACL_NO_RIGHTS;           /* keep compiler quiet */
+       return convert_any_priv_string(priv_type_text, language_priv_map);
 }
 
 
 /*
- * has_database_privilege variants
- *             These are all named "has_database_privilege" at the SQL level.
- *             They take various combinations of database name, database OID,
- *             user name, user sysid, or implicit user = current_user.
+ * has_schema_privilege variants
+ *             These are all named "has_schema_privilege" at the SQL level.
+ *             They take various combinations of schema name, schema OID,
+ *             user name, user OID, or implicit user = current_user.
  *
  *             The result is a boolean value: true if user has the indicated
- *             privilege, false if not.
+ *             privilege, false if not, or NULL if object doesn't exist.
  */
 
 /*
- * has_database_privilege_name_name
- *             Check user privileges on a database given
- *             name username, text databasename, and text priv name.
+ * has_schema_privilege_name_name
+ *             Check user privileges on a schema given
+ *             name username, text schemaname, and text priv name.
  */
 Datum
-has_database_privilege_name_name(PG_FUNCTION_ARGS)
+has_schema_privilege_name_name(PG_FUNCTION_ARGS)
 {
        Name            username = PG_GETARG_NAME(0);
-       text       *databasename = PG_GETARG_TEXT_P(1);
+       text       *schemaname = PG_GETARG_TEXT_P(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       int32           usesysid;
-       Oid                     databaseoid;
+       Oid                     roleid;
+       Oid                     schemaoid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = get_usesysid(NameStr(*username));
-       databaseoid = convert_database_name(databasename);
-       mode = convert_database_priv_string(priv_type_text);
+       roleid = get_roleid_checked(NameStr(*username));
+       schemaoid = convert_schema_name(schemaname);
+       mode = convert_schema_priv_string(priv_type_text);
 
-       aclresult = pg_database_aclcheck(databaseoid, usesysid, mode);
+       aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_database_privilege_name
- *             Check user privileges on a database given
- *             text databasename and text priv name.
+ * has_schema_privilege_name
+ *             Check user privileges on a schema given
+ *             text schemaname and text priv name.
  *             current_user is assumed
  */
 Datum
-has_database_privilege_name(PG_FUNCTION_ARGS)
+has_schema_privilege_name(PG_FUNCTION_ARGS)
 {
-       text       *databasename = PG_GETARG_TEXT_P(0);
+       text       *schemaname = PG_GETARG_TEXT_P(0);
        text       *priv_type_text = PG_GETARG_TEXT_P(1);
-       int32           usesysid;
-       Oid                     databaseoid;
+       Oid                     roleid;
+       Oid                     schemaoid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = GetUserId();
-       databaseoid = convert_database_name(databasename);
-       mode = convert_database_priv_string(priv_type_text);
+       roleid = GetUserId();
+       schemaoid = convert_schema_name(schemaname);
+       mode = convert_schema_priv_string(priv_type_text);
 
-       aclresult = pg_database_aclcheck(databaseoid, usesysid, mode);
+       aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_database_privilege_name_id
- *             Check user privileges on a database given
- *             name usename, database oid, and text priv name.
+ * has_schema_privilege_name_id
+ *             Check user privileges on a schema given
+ *             name usename, schema oid, and text priv name.
  */
 Datum
-has_database_privilege_name_id(PG_FUNCTION_ARGS)
+has_schema_privilege_name_id(PG_FUNCTION_ARGS)
 {
        Name            username = PG_GETARG_NAME(0);
-       Oid                     databaseoid = PG_GETARG_OID(1);
+       Oid                     schemaoid = PG_GETARG_OID(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       int32           usesysid;
+       Oid                     roleid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = get_usesysid(NameStr(*username));
-       mode = convert_database_priv_string(priv_type_text);
+       roleid = get_roleid_checked(NameStr(*username));
+       mode = convert_schema_priv_string(priv_type_text);
 
-       aclresult = pg_database_aclcheck(databaseoid, usesysid, mode);
+       if (!SearchSysCacheExists(NAMESPACEOID,
+                                                         ObjectIdGetDatum(schemaoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_database_privilege_id
- *             Check user privileges on a database given
- *             database oid, and text priv name.
+ * has_schema_privilege_id
+ *             Check user privileges on a schema given
+ *             schema oid, and text priv name.
  *             current_user is assumed
  */
 Datum
-has_database_privilege_id(PG_FUNCTION_ARGS)
+has_schema_privilege_id(PG_FUNCTION_ARGS)
 {
-       Oid                     databaseoid = PG_GETARG_OID(0);
+       Oid                     schemaoid = PG_GETARG_OID(0);
        text       *priv_type_text = PG_GETARG_TEXT_P(1);
-       int32           usesysid;
+       Oid                     roleid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = GetUserId();
-       mode = convert_database_priv_string(priv_type_text);
+       roleid = GetUserId();
+       mode = convert_schema_priv_string(priv_type_text);
+
+       if (!SearchSysCacheExists(NAMESPACEOID,
+                                                         ObjectIdGetDatum(schemaoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
 
-       aclresult = pg_database_aclcheck(databaseoid, usesysid, mode);
+       aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_database_privilege_id_name
- *             Check user privileges on a database given
- *             usesysid, text databasename, and text priv name.
+ * has_schema_privilege_id_name
+ *             Check user privileges on a schema given
+ *             roleid, text schemaname, and text priv name.
  */
 Datum
-has_database_privilege_id_name(PG_FUNCTION_ARGS)
+has_schema_privilege_id_name(PG_FUNCTION_ARGS)
 {
-       int32           usesysid = PG_GETARG_INT32(0);
-       text       *databasename = PG_GETARG_TEXT_P(1);
+       Oid                     roleid = PG_GETARG_OID(0);
+       text       *schemaname = PG_GETARG_TEXT_P(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       Oid                     databaseoid;
+       Oid                     schemaoid;
        AclMode         mode;
        AclResult       aclresult;
 
-       databaseoid = convert_database_name(databasename);
-       mode = convert_database_priv_string(priv_type_text);
+       schemaoid = convert_schema_name(schemaname);
+       mode = convert_schema_priv_string(priv_type_text);
 
-       aclresult = pg_database_aclcheck(databaseoid, usesysid, mode);
+       aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_database_privilege_id_id
- *             Check user privileges on a database given
- *             usesysid, database oid, and text priv name.
+ * has_schema_privilege_id_id
+ *             Check user privileges on a schema given
+ *             roleid, schema oid, and text priv name.
  */
 Datum
-has_database_privilege_id_id(PG_FUNCTION_ARGS)
+has_schema_privilege_id_id(PG_FUNCTION_ARGS)
 {
-       int32           usesysid = PG_GETARG_INT32(0);
-       Oid                     databaseoid = PG_GETARG_OID(1);
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     schemaoid = PG_GETARG_OID(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
        AclMode         mode;
        AclResult       aclresult;
 
-       mode = convert_database_priv_string(priv_type_text);
+       mode = convert_schema_priv_string(priv_type_text);
 
-       aclresult = pg_database_aclcheck(databaseoid, usesysid, mode);
+       if (!SearchSysCacheExists(NAMESPACEOID,
+                                                         ObjectIdGetDatum(schemaoid),
+                                                         0, 0, 0))
+               PG_RETURN_NULL();
+
+       aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- *             Support routines for has_database_privilege family.
+ *             Support routines for has_schema_privilege family.
  */
 
 /*
- * Given a database name expressed as a string, look it up and return Oid
+ * Given a schema name expressed as a string, look it up and return Oid
  */
 static Oid
-convert_database_name(text *databasename)
+convert_schema_name(text *schemaname)
 {
-       char       *dbname;
+       char       *nspname = text_to_cstring(schemaname);
        Oid                     oid;
 
-       dbname = DatumGetCString(DirectFunctionCall1(textout,
-                                                                                       PointerGetDatum(databasename)));
-
-       oid = get_database_oid(dbname);
+       oid = GetSysCacheOid(NAMESPACENAME,
+                                                CStringGetDatum(nspname),
+                                                0, 0, 0);
        if (!OidIsValid(oid))
-               elog(ERROR, "database \"%s\" does not exist", dbname);
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_SCHEMA),
+                                errmsg("schema \"%s\" does not exist", nspname)));
 
        return oid;
 }
 
 /*
- * convert_database_priv_string
+ * convert_schema_priv_string
  *             Convert text string to AclMode value.
  */
 static AclMode
-convert_database_priv_string(text *priv_type_text)
+convert_schema_priv_string(text *priv_type_text)
 {
-       char       *priv_type;
-
-       priv_type = DatumGetCString(DirectFunctionCall1(textout,
-                                                                                       PointerGetDatum(priv_type_text)));
-
-       /*
-        * Return mode from priv_type string
-        */
-       if (strcasecmp(priv_type, "CREATE") == 0)
-               return ACL_CREATE;
-
-       if (strcasecmp(priv_type, "TEMPORARY") == 0)
-               return ACL_CREATE_TEMP;
-
-       if (strcasecmp(priv_type, "TEMP") == 0)
-               return ACL_CREATE_TEMP;
-
-       elog(ERROR, "has_database_privilege: invalid privilege type %s",
-                priv_type);
-       return ACL_NO_RIGHTS;           /* keep compiler quiet */
+       static const priv_map schema_priv_map[] = {
+               {"CREATE", ACL_CREATE},
+               {"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
+               {"USAGE", ACL_USAGE},
+               {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)},
+               {NULL, 0}
+       };
+
+       return convert_any_priv_string(priv_type_text, schema_priv_map);
 }
 
 
 /*
- * has_function_privilege variants
- *             These are all named "has_function_privilege" at the SQL level.
- *             They take various combinations of function name, function OID,
- *             user name, user sysid, or implicit user = current_user.
+ * has_server_privilege variants
+ *             These are all named "has_server_privilege" at the SQL level.
+ *             They take various combinations of foreign server name,
+ *             server OID, user name, user OID, or implicit user = current_user.
  *
  *             The result is a boolean value: true if user has the indicated
  *             privilege, false if not.
  */
 
 /*
- * has_function_privilege_name_name
- *             Check user privileges on a function given
- *             name username, text functionname, and text priv name.
+ * has_server_privilege_name_name
+ *             Check user privileges on a foreign server given
+ *             name username, text servername, and text priv name.
  */
 Datum
-has_function_privilege_name_name(PG_FUNCTION_ARGS)
+has_server_privilege_name_name(PG_FUNCTION_ARGS)
 {
        Name            username = PG_GETARG_NAME(0);
-       text       *functionname = PG_GETARG_TEXT_P(1);
+       text       *servername = PG_GETARG_TEXT_P(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       int32           usesysid;
-       Oid                     functionoid;
+       Oid                     roleid;
+       Oid                     serverid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = get_usesysid(NameStr(*username));
-       functionoid = convert_function_name(functionname);
-       mode = convert_function_priv_string(priv_type_text);
+       roleid = get_roleid_checked(NameStr(*username));
+       serverid = convert_server_name(servername);
+       mode = convert_server_priv_string(priv_type_text);
 
-       aclresult = pg_proc_aclcheck(functionoid, usesysid, mode);
+       aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_function_privilege_name
- *             Check user privileges on a function given
- *             text functionname and text priv name.
+ * has_server_privilege_name
+ *             Check user privileges on a foreign server given
+ *             text servername and text priv name.
  *             current_user is assumed
  */
 Datum
-has_function_privilege_name(PG_FUNCTION_ARGS)
+has_server_privilege_name(PG_FUNCTION_ARGS)
 {
-       text       *functionname = PG_GETARG_TEXT_P(0);
+       text       *servername = PG_GETARG_TEXT_P(0);
        text       *priv_type_text = PG_GETARG_TEXT_P(1);
-       int32           usesysid;
-       Oid                     functionoid;
+       Oid                     roleid;
+       Oid                     serverid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = GetUserId();
-       functionoid = convert_function_name(functionname);
-       mode = convert_function_priv_string(priv_type_text);
+       roleid = GetUserId();
+       serverid = convert_server_name(servername);
+       mode = convert_server_priv_string(priv_type_text);
 
-       aclresult = pg_proc_aclcheck(functionoid, usesysid, mode);
+       aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_function_privilege_name_id
- *             Check user privileges on a function given
- *             name usename, function oid, and text priv name.
+ * has_server_privilege_name_id
+ *             Check user privileges on a foreign server given
+ *             name usename, foreign server oid, and text priv name.
  */
 Datum
-has_function_privilege_name_id(PG_FUNCTION_ARGS)
+has_server_privilege_name_id(PG_FUNCTION_ARGS)
 {
        Name            username = PG_GETARG_NAME(0);
-       Oid                     functionoid = PG_GETARG_OID(1);
+       Oid                     serverid = PG_GETARG_OID(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       int32           usesysid;
+       Oid                     roleid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = get_usesysid(NameStr(*username));
-       mode = convert_function_priv_string(priv_type_text);
+       roleid = get_roleid_checked(NameStr(*username));
+       mode = convert_server_priv_string(priv_type_text);
 
-       aclresult = pg_proc_aclcheck(functionoid, usesysid, mode);
+       aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_function_privilege_id
- *             Check user privileges on a function given
- *             function oid, and text priv name.
+ * has_server_privilege_id
+ *             Check user privileges on a foreign server given
+ *             server oid, and text priv name.
  *             current_user is assumed
  */
 Datum
-has_function_privilege_id(PG_FUNCTION_ARGS)
+has_server_privilege_id(PG_FUNCTION_ARGS)
 {
-       Oid                     functionoid = PG_GETARG_OID(0);
+       Oid                     serverid = PG_GETARG_OID(0);
        text       *priv_type_text = PG_GETARG_TEXT_P(1);
-       int32           usesysid;
+       Oid                     roleid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = GetUserId();
-       mode = convert_function_priv_string(priv_type_text);
+       roleid = GetUserId();
+       mode = convert_server_priv_string(priv_type_text);
 
-       aclresult = pg_proc_aclcheck(functionoid, usesysid, mode);
+       aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_function_privilege_id_name
- *             Check user privileges on a function given
- *             usesysid, text functionname, and text priv name.
+ * has_server_privilege_id_name
+ *             Check user privileges on a foreign server given
+ *             roleid, text servername, and text priv name.
  */
 Datum
-has_function_privilege_id_name(PG_FUNCTION_ARGS)
+has_server_privilege_id_name(PG_FUNCTION_ARGS)
 {
-       int32           usesysid = PG_GETARG_INT32(0);
-       text       *functionname = PG_GETARG_TEXT_P(1);
+       Oid                     roleid = PG_GETARG_OID(0);
+       text       *servername = PG_GETARG_TEXT_P(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       Oid                     functionoid;
+       Oid                     serverid;
        AclMode         mode;
        AclResult       aclresult;
 
-       functionoid = convert_function_name(functionname);
-       mode = convert_function_priv_string(priv_type_text);
+       serverid = convert_server_name(servername);
+       mode = convert_server_priv_string(priv_type_text);
 
-       aclresult = pg_proc_aclcheck(functionoid, usesysid, mode);
+       aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_function_privilege_id_id
- *             Check user privileges on a function given
- *             usesysid, function oid, and text priv name.
+ * has_server_privilege_id_id
+ *             Check user privileges on a foreign server given
+ *             roleid, server oid, and text priv name.
  */
 Datum
-has_function_privilege_id_id(PG_FUNCTION_ARGS)
+has_server_privilege_id_id(PG_FUNCTION_ARGS)
 {
-       int32           usesysid = PG_GETARG_INT32(0);
-       Oid                     functionoid = PG_GETARG_OID(1);
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     serverid = PG_GETARG_OID(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
        AclMode         mode;
        AclResult       aclresult;
 
-       mode = convert_function_priv_string(priv_type_text);
+       mode = convert_server_priv_string(priv_type_text);
 
-       aclresult = pg_proc_aclcheck(functionoid, usesysid, mode);
+       aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- *             Support routines for has_function_privilege family.
+ *             Support routines for has_server_privilege family.
  */
 
 /*
- * Given a function name expressed as a string, look it up and return Oid
+ * Given a server name expressed as a string, look it up and return Oid
  */
 static Oid
-convert_function_name(text *functionname)
+convert_server_name(text *servername)
 {
-       char       *funcname;
-       Oid                     oid;
-
-       funcname = DatumGetCString(DirectFunctionCall1(textout,
-                                                                                       PointerGetDatum(functionname)));
-
-       oid = DatumGetObjectId(DirectFunctionCall1(regprocedurein,
-                                                                                          CStringGetDatum(funcname)));
-
-       if (!OidIsValid(oid))
-               elog(ERROR, "function \"%s\" does not exist", funcname);
+       char       *serverstr = text_to_cstring(servername);
 
-       return oid;
+       return GetForeignServerOidByName(serverstr, false);
 }
 
 /*
- * convert_function_priv_string
+ * convert_server_priv_string
  *             Convert text string to AclMode value.
  */
 static AclMode
-convert_function_priv_string(text *priv_type_text)
+convert_server_priv_string(text *priv_type_text)
 {
-       char       *priv_type;
-
-       priv_type = DatumGetCString(DirectFunctionCall1(textout,
-                                                                                       PointerGetDatum(priv_type_text)));
-
-       /*
-        * Return mode from priv_type string
-        */
-       if (strcasecmp(priv_type, "EXECUTE") == 0)
-               return ACL_EXECUTE;
+       static const priv_map server_priv_map[] = {
+               {"USAGE", ACL_USAGE},
+               {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)},
+               {NULL, 0}
+       };
 
-       elog(ERROR, "has_function_privilege: invalid privilege type %s",
-                priv_type);
-       return ACL_NO_RIGHTS;           /* keep compiler quiet */
+       return convert_any_priv_string(priv_type_text, server_priv_map);
 }
 
 
 /*
- * has_language_privilege variants
- *             These are all named "has_language_privilege" at the SQL level.
- *             They take various combinations of language name, language OID,
- *             user name, user sysid, or implicit user = current_user.
+ * has_tablespace_privilege variants
+ *             These are all named "has_tablespace_privilege" at the SQL level.
+ *             They take various combinations of tablespace name, tablespace OID,
+ *             user name, user OID, or implicit user = current_user.
  *
  *             The result is a boolean value: true if user has the indicated
  *             privilege, false if not.
  */
 
 /*
- * has_language_privilege_name_name
- *             Check user privileges on a language given
- *             name username, text languagename, and text priv name.
+ * has_tablespace_privilege_name_name
+ *             Check user privileges on a tablespace given
+ *             name username, text tablespacename, and text priv name.
  */
 Datum
-has_language_privilege_name_name(PG_FUNCTION_ARGS)
+has_tablespace_privilege_name_name(PG_FUNCTION_ARGS)
 {
        Name            username = PG_GETARG_NAME(0);
-       text       *languagename = PG_GETARG_TEXT_P(1);
+       text       *tablespacename = PG_GETARG_TEXT_P(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       int32           usesysid;
-       Oid                     languageoid;
+       Oid                     roleid;
+       Oid                     tablespaceoid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = get_usesysid(NameStr(*username));
-       languageoid = convert_language_name(languagename);
-       mode = convert_language_priv_string(priv_type_text);
+       roleid = get_roleid_checked(NameStr(*username));
+       tablespaceoid = convert_tablespace_name(tablespacename);
+       mode = convert_tablespace_priv_string(priv_type_text);
 
-       aclresult = pg_language_aclcheck(languageoid, usesysid, mode);
+       aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_language_privilege_name
- *             Check user privileges on a language given
- *             text languagename and text priv name.
+ * has_tablespace_privilege_name
+ *             Check user privileges on a tablespace given
+ *             text tablespacename and text priv name.
  *             current_user is assumed
  */
 Datum
-has_language_privilege_name(PG_FUNCTION_ARGS)
+has_tablespace_privilege_name(PG_FUNCTION_ARGS)
 {
-       text       *languagename = PG_GETARG_TEXT_P(0);
+       text       *tablespacename = PG_GETARG_TEXT_P(0);
        text       *priv_type_text = PG_GETARG_TEXT_P(1);
-       int32           usesysid;
-       Oid                     languageoid;
+       Oid                     roleid;
+       Oid                     tablespaceoid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = GetUserId();
-       languageoid = convert_language_name(languagename);
-       mode = convert_language_priv_string(priv_type_text);
+       roleid = GetUserId();
+       tablespaceoid = convert_tablespace_name(tablespacename);
+       mode = convert_tablespace_priv_string(priv_type_text);
 
-       aclresult = pg_language_aclcheck(languageoid, usesysid, mode);
+       aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_language_privilege_name_id
- *             Check user privileges on a language given
- *             name usename, language oid, and text priv name.
+ * has_tablespace_privilege_name_id
+ *             Check user privileges on a tablespace given
+ *             name usename, tablespace oid, and text priv name.
  */
 Datum
-has_language_privilege_name_id(PG_FUNCTION_ARGS)
+has_tablespace_privilege_name_id(PG_FUNCTION_ARGS)
 {
        Name            username = PG_GETARG_NAME(0);
-       Oid                     languageoid = PG_GETARG_OID(1);
+       Oid                     tablespaceoid = PG_GETARG_OID(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       int32           usesysid;
+       Oid                     roleid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = get_usesysid(NameStr(*username));
-       mode = convert_language_priv_string(priv_type_text);
+       roleid = get_roleid_checked(NameStr(*username));
+       mode = convert_tablespace_priv_string(priv_type_text);
 
-       aclresult = pg_language_aclcheck(languageoid, usesysid, mode);
+       aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_language_privilege_id
- *             Check user privileges on a language given
- *             language oid, and text priv name.
+ * has_tablespace_privilege_id
+ *             Check user privileges on a tablespace given
+ *             tablespace oid, and text priv name.
  *             current_user is assumed
  */
 Datum
-has_language_privilege_id(PG_FUNCTION_ARGS)
+has_tablespace_privilege_id(PG_FUNCTION_ARGS)
 {
-       Oid                     languageoid = PG_GETARG_OID(0);
+       Oid                     tablespaceoid = PG_GETARG_OID(0);
        text       *priv_type_text = PG_GETARG_TEXT_P(1);
-       int32           usesysid;
+       Oid                     roleid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = GetUserId();
-       mode = convert_language_priv_string(priv_type_text);
+       roleid = GetUserId();
+       mode = convert_tablespace_priv_string(priv_type_text);
 
-       aclresult = pg_language_aclcheck(languageoid, usesysid, mode);
+       aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_language_privilege_id_name
- *             Check user privileges on a language given
- *             usesysid, text languagename, and text priv name.
+ * has_tablespace_privilege_id_name
+ *             Check user privileges on a tablespace given
+ *             roleid, text tablespacename, and text priv name.
  */
 Datum
-has_language_privilege_id_name(PG_FUNCTION_ARGS)
+has_tablespace_privilege_id_name(PG_FUNCTION_ARGS)
 {
-       int32           usesysid = PG_GETARG_INT32(0);
-       text       *languagename = PG_GETARG_TEXT_P(1);
+       Oid                     roleid = PG_GETARG_OID(0);
+       text       *tablespacename = PG_GETARG_TEXT_P(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       Oid                     languageoid;
+       Oid                     tablespaceoid;
        AclMode         mode;
        AclResult       aclresult;
 
-       languageoid = convert_language_name(languagename);
-       mode = convert_language_priv_string(priv_type_text);
+       tablespaceoid = convert_tablespace_name(tablespacename);
+       mode = convert_tablespace_priv_string(priv_type_text);
 
-       aclresult = pg_language_aclcheck(languageoid, usesysid, mode);
+       aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_language_privilege_id_id
- *             Check user privileges on a language given
- *             usesysid, language oid, and text priv name.
+ * has_tablespace_privilege_id_id
+ *             Check user privileges on a tablespace given
+ *             roleid, tablespace oid, and text priv name.
  */
 Datum
-has_language_privilege_id_id(PG_FUNCTION_ARGS)
+has_tablespace_privilege_id_id(PG_FUNCTION_ARGS)
 {
-       int32           usesysid = PG_GETARG_INT32(0);
-       Oid                     languageoid = PG_GETARG_OID(1);
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     tablespaceoid = PG_GETARG_OID(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
        AclMode         mode;
        AclResult       aclresult;
 
-       mode = convert_language_priv_string(priv_type_text);
+       mode = convert_tablespace_priv_string(priv_type_text);
 
-       aclresult = pg_language_aclcheck(languageoid, usesysid, mode);
+       aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- *             Support routines for has_language_privilege family.
+ *             Support routines for has_tablespace_privilege family.
  */
 
 /*
- * Given a language name expressed as a string, look it up and return Oid
+ * Given a tablespace name expressed as a string, look it up and return Oid
  */
 static Oid
-convert_language_name(text *languagename)
+convert_tablespace_name(text *tablespacename)
 {
-       char       *langname;
+       char       *spcname = text_to_cstring(tablespacename);
        Oid                     oid;
 
-       langname = DatumGetCString(DirectFunctionCall1(textout,
-                                                                                       PointerGetDatum(languagename)));
+       oid = get_tablespace_oid(spcname);
 
-       oid = GetSysCacheOid(LANGNAME,
-                                                CStringGetDatum(langname),
-                                                0, 0, 0);
        if (!OidIsValid(oid))
-               elog(ERROR, "language \"%s\" does not exist", langname);
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                errmsg("tablespace \"%s\" does not exist", spcname)));
 
        return oid;
 }
 
 /*
- * convert_language_priv_string
+ * convert_tablespace_priv_string
  *             Convert text string to AclMode value.
  */
 static AclMode
-convert_language_priv_string(text *priv_type_text)
+convert_tablespace_priv_string(text *priv_type_text)
 {
-       char       *priv_type;
-
-       priv_type = DatumGetCString(DirectFunctionCall1(textout,
-                                                                                       PointerGetDatum(priv_type_text)));
-
-       /*
-        * Return mode from priv_type string
-        */
-       if (strcasecmp(priv_type, "USAGE") == 0)
-               return ACL_USAGE;
+       static const priv_map tablespace_priv_map[] = {
+               {"CREATE", ACL_CREATE},
+               {"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
+               {NULL, 0}
+       };
 
-       elog(ERROR, "has_language_privilege: invalid privilege type %s",
-                priv_type);
-       return ACL_NO_RIGHTS;           /* keep compiler quiet */
+       return convert_any_priv_string(priv_type_text, tablespace_priv_map);
 }
 
-
 /*
- * has_schema_privilege variants
- *             These are all named "has_schema_privilege" at the SQL level.
- *             They take various combinations of schema name, schema OID,
- *             user name, user sysid, or implicit user = current_user.
+ * pg_has_role variants
+ *             These are all named "pg_has_role" at the SQL level.
+ *             They take various combinations of role name, role OID,
+ *             user name, user OID, or implicit user = current_user.
  *
  *             The result is a boolean value: true if user has the indicated
  *             privilege, false if not.
  */
 
 /*
- * has_schema_privilege_name_name
- *             Check user privileges on a schema given
- *             name username, text schemaname, and text priv name.
+ * pg_has_role_name_name
+ *             Check user privileges on a role given
+ *             name username, name rolename, and text priv name.
  */
 Datum
-has_schema_privilege_name_name(PG_FUNCTION_ARGS)
+pg_has_role_name_name(PG_FUNCTION_ARGS)
 {
        Name            username = PG_GETARG_NAME(0);
-       text       *schemaname = PG_GETARG_TEXT_P(1);
+       Name            rolename = PG_GETARG_NAME(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       int32           usesysid;
-       Oid                     schemaoid;
+       Oid                     roleid;
+       Oid                     roleoid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = get_usesysid(NameStr(*username));
-       schemaoid = convert_schema_name(schemaname);
-       mode = convert_schema_priv_string(priv_type_text);
+       roleid = get_roleid_checked(NameStr(*username));
+       roleoid = get_roleid_checked(NameStr(*rolename));
+       mode = convert_role_priv_string(priv_type_text);
 
-       aclresult = pg_namespace_aclcheck(schemaoid, usesysid, mode);
+       aclresult = pg_role_aclcheck(roleoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_schema_privilege_name
- *             Check user privileges on a schema given
- *             text schemaname and text priv name.
+ * pg_has_role_name
+ *             Check user privileges on a role given
+ *             name rolename and text priv name.
  *             current_user is assumed
  */
 Datum
-has_schema_privilege_name(PG_FUNCTION_ARGS)
+pg_has_role_name(PG_FUNCTION_ARGS)
 {
-       text       *schemaname = PG_GETARG_TEXT_P(0);
+       Name            rolename = PG_GETARG_NAME(0);
        text       *priv_type_text = PG_GETARG_TEXT_P(1);
-       int32           usesysid;
-       Oid                     schemaoid;
+       Oid                     roleid;
+       Oid                     roleoid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = GetUserId();
-       schemaoid = convert_schema_name(schemaname);
-       mode = convert_schema_priv_string(priv_type_text);
+       roleid = GetUserId();
+       roleoid = get_roleid_checked(NameStr(*rolename));
+       mode = convert_role_priv_string(priv_type_text);
 
-       aclresult = pg_namespace_aclcheck(schemaoid, usesysid, mode);
+       aclresult = pg_role_aclcheck(roleoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_schema_privilege_name_id
- *             Check user privileges on a schema given
- *             name usename, schema oid, and text priv name.
+ * pg_has_role_name_id
+ *             Check user privileges on a role given
+ *             name usename, role oid, and text priv name.
  */
 Datum
-has_schema_privilege_name_id(PG_FUNCTION_ARGS)
+pg_has_role_name_id(PG_FUNCTION_ARGS)
 {
        Name            username = PG_GETARG_NAME(0);
-       Oid                     schemaoid = PG_GETARG_OID(1);
+       Oid                     roleoid = PG_GETARG_OID(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       int32           usesysid;
+       Oid                     roleid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = get_usesysid(NameStr(*username));
-       mode = convert_schema_priv_string(priv_type_text);
+       roleid = get_roleid_checked(NameStr(*username));
+       mode = convert_role_priv_string(priv_type_text);
 
-       aclresult = pg_namespace_aclcheck(schemaoid, usesysid, mode);
+       aclresult = pg_role_aclcheck(roleoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_schema_privilege_id
- *             Check user privileges on a schema given
- *             schema oid, and text priv name.
+ * pg_has_role_id
+ *             Check user privileges on a role given
+ *             role oid, and text priv name.
  *             current_user is assumed
  */
 Datum
-has_schema_privilege_id(PG_FUNCTION_ARGS)
+pg_has_role_id(PG_FUNCTION_ARGS)
 {
-       Oid                     schemaoid = PG_GETARG_OID(0);
+       Oid                     roleoid = PG_GETARG_OID(0);
        text       *priv_type_text = PG_GETARG_TEXT_P(1);
-       int32           usesysid;
+       Oid                     roleid;
        AclMode         mode;
        AclResult       aclresult;
 
-       usesysid = GetUserId();
-       mode = convert_schema_priv_string(priv_type_text);
+       roleid = GetUserId();
+       mode = convert_role_priv_string(priv_type_text);
 
-       aclresult = pg_namespace_aclcheck(schemaoid, usesysid, mode);
+       aclresult = pg_role_aclcheck(roleoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_schema_privilege_id_name
- *             Check user privileges on a schema given
- *             usesysid, text schemaname, and text priv name.
+ * pg_has_role_id_name
+ *             Check user privileges on a role given
+ *             roleid, name rolename, and text priv name.
  */
 Datum
-has_schema_privilege_id_name(PG_FUNCTION_ARGS)
+pg_has_role_id_name(PG_FUNCTION_ARGS)
 {
-       int32           usesysid = PG_GETARG_INT32(0);
-       text       *schemaname = PG_GETARG_TEXT_P(1);
+       Oid                     roleid = PG_GETARG_OID(0);
+       Name            rolename = PG_GETARG_NAME(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
-       Oid                     schemaoid;
+       Oid                     roleoid;
        AclMode         mode;
        AclResult       aclresult;
 
-       schemaoid = convert_schema_name(schemaname);
-       mode = convert_schema_priv_string(priv_type_text);
+       roleoid = get_roleid_checked(NameStr(*rolename));
+       mode = convert_role_priv_string(priv_type_text);
 
-       aclresult = pg_namespace_aclcheck(schemaoid, usesysid, mode);
+       aclresult = pg_role_aclcheck(roleoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- * has_schema_privilege_id_id
- *             Check user privileges on a schema given
- *             usesysid, schema oid, and text priv name.
+ * pg_has_role_id_id
+ *             Check user privileges on a role given
+ *             roleid, role oid, and text priv name.
  */
 Datum
-has_schema_privilege_id_id(PG_FUNCTION_ARGS)
+pg_has_role_id_id(PG_FUNCTION_ARGS)
 {
-       int32           usesysid = PG_GETARG_INT32(0);
-       Oid                     schemaoid = PG_GETARG_OID(1);
+       Oid                     roleid = PG_GETARG_OID(0);
+       Oid                     roleoid = PG_GETARG_OID(1);
        text       *priv_type_text = PG_GETARG_TEXT_P(2);
        AclMode         mode;
        AclResult       aclresult;
 
-       mode = convert_schema_priv_string(priv_type_text);
+       mode = convert_role_priv_string(priv_type_text);
 
-       aclresult = pg_namespace_aclcheck(schemaoid, usesysid, mode);
+       aclresult = pg_role_aclcheck(roleoid, roleid, mode);
 
        PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
 }
 
 /*
- *             Support routines for has_schema_privilege family.
+ *             Support routines for pg_has_role family.
  */
 
 /*
- * Given a schema name expressed as a string, look it up and return Oid
+ * convert_role_priv_string
+ *             Convert text string to AclMode value.
+ *
+ * We use USAGE to denote whether the privileges of the role are accessible
+ * (has_privs), MEMBER to denote is_member, and MEMBER WITH GRANT OPTION
+ * (or ADMIN OPTION) to denote is_admin.  There is no ACL bit corresponding
+ * to MEMBER so we cheat and use ACL_CREATE for that.  This convention
+ * is shared only with pg_role_aclcheck, below.
  */
-static Oid
-convert_schema_name(text *schemaname)
+static AclMode
+convert_role_priv_string(text *priv_type_text)
 {
-       char       *nspname;
-       Oid                     oid;
+       static const priv_map role_priv_map[] = {
+               {"USAGE", ACL_USAGE},
+               {"MEMBER", ACL_CREATE},
+               {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
+               {"USAGE WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
+               {"MEMBER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
+               {"MEMBER WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
+               {NULL, 0}
+       };
+
+       return convert_any_priv_string(priv_type_text, role_priv_map);
+}
 
-       nspname = DatumGetCString(DirectFunctionCall1(textout,
-                                                                                       PointerGetDatum(schemaname)));
+/*
+ * pg_role_aclcheck
+ *             Quick-and-dirty support for pg_has_role
+ */
+static AclResult
+pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode)
+{
+       if (mode & ACL_GRANT_OPTION_FOR(ACL_CREATE))
+       {
+               if (is_admin_of_role(roleid, role_oid))
+                       return ACLCHECK_OK;
+       }
+       if (mode & ACL_CREATE)
+       {
+               if (is_member_of_role(roleid, role_oid))
+                       return ACLCHECK_OK;
+       }
+       if (mode & ACL_USAGE)
+       {
+               if (has_privs_of_role(roleid, role_oid))
+                       return ACLCHECK_OK;
+       }
+       return ACLCHECK_NO_PRIV;
+}
 
-       oid = GetSysCacheOid(NAMESPACENAME,
-                                                CStringGetDatum(nspname),
-                                                0, 0, 0);
-       if (!OidIsValid(oid))
-               elog(ERROR, "schema \"%s\" does not exist", nspname);
 
-       return oid;
+/*
+ * initialization function (called by InitPostgres)
+ */
+void
+initialize_acl(void)
+{
+       if (!IsBootstrapProcessingMode())
+       {
+               /*
+                * In normal mode, set a callback on any syscache invalidation of
+                * pg_auth_members rows
+                */
+               CacheRegisterSyscacheCallback(AUTHMEMROLEMEM,
+                                                                         RoleMembershipCacheCallback,
+                                                                         (Datum) 0);
+       }
 }
 
 /*
- * convert_schema_priv_string
- *             Convert text string to AclMode value.
+ * RoleMembershipCacheCallback
+ *             Syscache inval callback function
  */
-static AclMode
-convert_schema_priv_string(text *priv_type_text)
+static void
+RoleMembershipCacheCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
+{
+       /* Force membership caches to be recomputed on next use */
+       cached_privs_role = InvalidOid;
+       cached_member_role = InvalidOid;
+}
+
+
+/* Check if specified role has rolinherit set */
+static bool
+has_rolinherit(Oid roleid)
+{
+       bool            result = false;
+       HeapTuple       utup;
+
+       utup = SearchSysCache(AUTHOID,
+                                                 ObjectIdGetDatum(roleid),
+                                                 0, 0, 0);
+       if (HeapTupleIsValid(utup))
+       {
+               result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
+               ReleaseSysCache(utup);
+       }
+       return result;
+}
+
+
+/*
+ * Get a list of roles that the specified roleid has the privileges of
+ *
+ * This is defined not to recurse through roles that don't have rolinherit
+ * set; for such roles, membership implies the ability to do SET ROLE, but
+ * the privileges are not available until you've done so.
+ *
+ * Since indirect membership testing is relatively expensive, we cache
+ * a list of memberships.  Hence, the result is only guaranteed good until
+ * the next call of roles_has_privs_of()!
+ *
+ * For the benefit of select_best_grantor, the result is defined to be
+ * in breadth-first order, ie, closer relationships earlier.
+ */
+static List *
+roles_has_privs_of(Oid roleid)
 {
-       char       *priv_type;
+       List       *roles_list;
+       ListCell   *l;
+       List       *new_cached_privs_roles;
+       MemoryContext oldctx;
 
-       priv_type = DatumGetCString(DirectFunctionCall1(textout,
-                                                                                       PointerGetDatum(priv_type_text)));
+       /* If cache is already valid, just return the list */
+       if (OidIsValid(cached_privs_role) && cached_privs_role == roleid)
+               return cached_privs_roles;
 
        /*
-        * Return mode from priv_type string
+        * Find all the roles that roleid is a member of, including multi-level
+        * recursion.  The role itself will always be the first element of the
+        * resulting list.
+        *
+        * Each element of the list is scanned to see if it adds any indirect
+        * memberships.  We can use a single list as both the record of
+        * already-found memberships and the agenda of roles yet to be scanned.
+        * This is a bit tricky but works because the foreach() macro doesn't
+        * fetch the next list element until the bottom of the loop.
         */
-       if (strcasecmp(priv_type, "CREATE") == 0)
-               return ACL_CREATE;
+       roles_list = list_make1_oid(roleid);
 
-       if (strcasecmp(priv_type, "USAGE") == 0)
-               return ACL_USAGE;
+       foreach(l, roles_list)
+       {
+               Oid                     memberid = lfirst_oid(l);
+               CatCList   *memlist;
+               int                     i;
+
+               /* Ignore non-inheriting roles */
+               if (!has_rolinherit(memberid))
+                       continue;
+
+               /* Find roles that memberid is directly a member of */
+               memlist = SearchSysCacheList(AUTHMEMMEMROLE, 1,
+                                                                        ObjectIdGetDatum(memberid),
+                                                                        0, 0, 0);
+               for (i = 0; i < memlist->n_members; i++)
+               {
+                       HeapTuple       tup = &memlist->members[i]->tuple;
+                       Oid                     otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
+
+                       /*
+                        * Even though there shouldn't be any loops in the membership
+                        * graph, we must test for having already seen this role. It is
+                        * legal for instance to have both A->B and A->C->B.
+                        */
+                       roles_list = list_append_unique_oid(roles_list, otherid);
+               }
+               ReleaseSysCacheList(memlist);
+       }
 
-       elog(ERROR, "has_schema_privilege: invalid privilege type %s",
-                priv_type);
-       return ACL_NO_RIGHTS;           /* keep compiler quiet */
+       /*
+        * Copy the completed list into TopMemoryContext so it will persist.
+        */
+       oldctx = MemoryContextSwitchTo(TopMemoryContext);
+       new_cached_privs_roles = list_copy(roles_list);
+       MemoryContextSwitchTo(oldctx);
+       list_free(roles_list);
+
+       /*
+        * Now safe to assign to state variable
+        */
+       cached_privs_role = InvalidOid;         /* just paranoia */
+       list_free(cached_privs_roles);
+       cached_privs_roles = new_cached_privs_roles;
+       cached_privs_role = roleid;
+
+       /* And now we can return the answer */
+       return cached_privs_roles;
+}
+
+
+/*
+ * Get a list of roles that the specified roleid is a member of
+ *
+ * This is defined to recurse through roles regardless of rolinherit.
+ *
+ * Since indirect membership testing is relatively expensive, we cache
+ * a list of memberships.  Hence, the result is only guaranteed good until
+ * the next call of roles_is_member_of()!
+ */
+static List *
+roles_is_member_of(Oid roleid)
+{
+       List       *roles_list;
+       ListCell   *l;
+       List       *new_cached_membership_roles;
+       MemoryContext oldctx;
+
+       /* If cache is already valid, just return the list */
+       if (OidIsValid(cached_member_role) && cached_member_role == roleid)
+               return cached_membership_roles;
+
+       /*
+        * Find all the roles that roleid is a member of, including multi-level
+        * recursion.  The role itself will always be the first element of the
+        * resulting list.
+        *
+        * Each element of the list is scanned to see if it adds any indirect
+        * memberships.  We can use a single list as both the record of
+        * already-found memberships and the agenda of roles yet to be scanned.
+        * This is a bit tricky but works because the foreach() macro doesn't
+        * fetch the next list element until the bottom of the loop.
+        */
+       roles_list = list_make1_oid(roleid);
+
+       foreach(l, roles_list)
+       {
+               Oid                     memberid = lfirst_oid(l);
+               CatCList   *memlist;
+               int                     i;
+
+               /* Find roles that memberid is directly a member of */
+               memlist = SearchSysCacheList(AUTHMEMMEMROLE, 1,
+                                                                        ObjectIdGetDatum(memberid),
+                                                                        0, 0, 0);
+               for (i = 0; i < memlist->n_members; i++)
+               {
+                       HeapTuple       tup = &memlist->members[i]->tuple;
+                       Oid                     otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
+
+                       /*
+                        * Even though there shouldn't be any loops in the membership
+                        * graph, we must test for having already seen this role. It is
+                        * legal for instance to have both A->B and A->C->B.
+                        */
+                       roles_list = list_append_unique_oid(roles_list, otherid);
+               }
+               ReleaseSysCacheList(memlist);
+       }
+
+       /*
+        * Copy the completed list into TopMemoryContext so it will persist.
+        */
+       oldctx = MemoryContextSwitchTo(TopMemoryContext);
+       new_cached_membership_roles = list_copy(roles_list);
+       MemoryContextSwitchTo(oldctx);
+       list_free(roles_list);
+
+       /*
+        * Now safe to assign to state variable
+        */
+       cached_member_role = InvalidOid;        /* just paranoia */
+       list_free(cached_membership_roles);
+       cached_membership_roles = new_cached_membership_roles;
+       cached_member_role = roleid;
+
+       /* And now we can return the answer */
+       return cached_membership_roles;
+}
+
+
+/*
+ * Does member have the privileges of role (directly or indirectly)?
+ *
+ * This is defined not to recurse through roles that don't have rolinherit
+ * set; for such roles, membership implies the ability to do SET ROLE, but
+ * the privileges are not available until you've done so.
+ */
+bool
+has_privs_of_role(Oid member, Oid role)
+{
+       /* Fast path for simple case */
+       if (member == role)
+               return true;
+
+       /* Superusers have every privilege, so are part of every role */
+       if (superuser_arg(member))
+               return true;
+
+       /*
+        * Find all the roles that member has the privileges of, including
+        * multi-level recursion, then see if target role is any one of them.
+        */
+       return list_member_oid(roles_has_privs_of(member), role);
+}
+
+
+/*
+ * Is member a member of role (directly or indirectly)?
+ *
+ * This is defined to recurse through roles regardless of rolinherit.
+ */
+bool
+is_member_of_role(Oid member, Oid role)
+{
+       /* Fast path for simple case */
+       if (member == role)
+               return true;
+
+       /* Superusers have every privilege, so are part of every role */
+       if (superuser_arg(member))
+               return true;
+
+       /*
+        * Find all the roles that member is a member of, including multi-level
+        * recursion, then see if target role is any one of them.
+        */
+       return list_member_oid(roles_is_member_of(member), role);
+}
+
+/*
+ * check_is_member_of_role
+ *             is_member_of_role with a standard permission-violation error if not
+ */
+void
+check_is_member_of_role(Oid member, Oid role)
+{
+       if (!is_member_of_role(member, role))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                errmsg("must be member of role \"%s\"",
+                                               GetUserNameFromId(role))));
+}
+
+/*
+ * Is member a member of role, not considering superuserness?
+ *
+ * This is identical to is_member_of_role except we ignore superuser
+ * status.
+ */
+bool
+is_member_of_role_nosuper(Oid member, Oid role)
+{
+       /* Fast path for simple case */
+       if (member == role)
+               return true;
+
+       /*
+        * Find all the roles that member is a member of, including multi-level
+        * recursion, then see if target role is any one of them.
+        */
+       return list_member_oid(roles_is_member_of(member), role);
+}
+
+
+/*
+ * Is member an admin of role (directly or indirectly)?  That is, is it
+ * a member WITH ADMIN OPTION?
+ *
+ * We could cache the result as for is_member_of_role, but currently this
+ * is not used in any performance-critical paths, so we don't.
+ */
+bool
+is_admin_of_role(Oid member, Oid role)
+{
+       bool            result = false;
+       List       *roles_list;
+       ListCell   *l;
+
+       /* Fast path for simple case */
+       if (member == role)
+               return true;
+
+       /* Superusers have every privilege, so are part of every role */
+       if (superuser_arg(member))
+               return true;
+
+       /*
+        * Find all the roles that member is a member of, including multi-level
+        * recursion.  We build a list in the same way that is_member_of_role does
+        * to track visited and unvisited roles.
+        */
+       roles_list = list_make1_oid(member);
+
+       foreach(l, roles_list)
+       {
+               Oid                     memberid = lfirst_oid(l);
+               CatCList   *memlist;
+               int                     i;
+
+               /* Find roles that memberid is directly a member of */
+               memlist = SearchSysCacheList(AUTHMEMMEMROLE, 1,
+                                                                        ObjectIdGetDatum(memberid),
+                                                                        0, 0, 0);
+               for (i = 0; i < memlist->n_members; i++)
+               {
+                       HeapTuple       tup = &memlist->members[i]->tuple;
+                       Oid                     otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
+
+                       if (otherid == role &&
+                               ((Form_pg_auth_members) GETSTRUCT(tup))->admin_option)
+                       {
+                               /* Found what we came for, so can stop searching */
+                               result = true;
+                               break;
+                       }
+
+                       roles_list = list_append_unique_oid(roles_list, otherid);
+               }
+               ReleaseSysCacheList(memlist);
+               if (result)
+                       break;
+       }
+
+       list_free(roles_list);
+
+       return result;
+}
+
+
+/* does what it says ... */
+static int
+count_one_bits(AclMode mask)
+{
+       int                     nbits = 0;
+
+       /* this code relies on AclMode being an unsigned type */
+       while (mask)
+       {
+               if (mask & 1)
+                       nbits++;
+               mask >>= 1;
+       }
+       return nbits;
+}
+
+
+/*
+ * Select the effective grantor ID for a GRANT or REVOKE operation.
+ *
+ * The grantor must always be either the object owner or some role that has
+ * been explicitly granted grant options.  This ensures that all granted
+ * privileges appear to flow from the object owner, and there are never
+ * multiple "original sources" of a privilege. Therefore, if the would-be
+ * grantor is a member of a role that has the needed grant options, we have
+ * to do the grant as that role instead.
+ *
+ * It is possible that the would-be grantor is a member of several roles
+ * that have different subsets of the desired grant options, but no one
+ * role has 'em all.  In this case we pick a role with the largest number
+ * of desired options. Ties are broken in favor of closer ancestors.
+ *
+ * roleId: the role attempting to do the GRANT/REVOKE
+ * privileges: the privileges to be granted/revoked
+ * acl: the ACL of the object in question
+ * ownerId: the role owning the object in question
+ * *grantorId: receives the OID of the role to do the grant as
+ * *grantOptions: receives the grant options actually held by grantorId
+ *
+ * If no grant options exist, we set grantorId to roleId, grantOptions to 0.
+ */
+void
+select_best_grantor(Oid roleId, AclMode privileges,
+                                       const Acl *acl, Oid ownerId,
+                                       Oid *grantorId, AclMode *grantOptions)
+{
+       AclMode         needed_goptions = ACL_GRANT_OPTION_FOR(privileges);
+       List       *roles_list;
+       int                     nrights;
+       ListCell   *l;
+
+       /*
+        * The object owner is always treated as having all grant options, so if
+        * roleId is the owner it's easy.  Also, if roleId is a superuser it's
+        * easy: superusers are implicitly members of every role, so they act as
+        * the object owner.
+        */
+       if (roleId == ownerId || superuser_arg(roleId))
+       {
+               *grantorId = ownerId;
+               *grantOptions = needed_goptions;
+               return;
+       }
+
+       /*
+        * Otherwise we have to do a careful search to see if roleId has the
+        * privileges of any suitable role.  Note: we can hang onto the result of
+        * roles_has_privs_of() throughout this loop, because aclmask_direct()
+        * doesn't query any role memberships.
+        */
+       roles_list = roles_has_privs_of(roleId);
+
+       /* initialize candidate result as default */
+       *grantorId = roleId;
+       *grantOptions = ACL_NO_RIGHTS;
+       nrights = 0;
+
+       foreach(l, roles_list)
+       {
+               Oid                     otherrole = lfirst_oid(l);
+               AclMode         otherprivs;
+
+               otherprivs = aclmask_direct(acl, otherrole, ownerId,
+                                                                       needed_goptions, ACLMASK_ALL);
+               if (otherprivs == needed_goptions)
+               {
+                       /* Found a suitable grantor */
+                       *grantorId = otherrole;
+                       *grantOptions = otherprivs;
+                       return;
+               }
+
+               /*
+                * If it has just some of the needed privileges, remember best
+                * candidate.
+                */
+               if (otherprivs != ACL_NO_RIGHTS)
+               {
+                       int                     nnewrights = count_one_bits(otherprivs);
+
+                       if (nnewrights > nrights)
+                       {
+                               *grantorId = otherrole;
+                               *grantOptions = otherprivs;
+                               nrights = nnewrights;
+                       }
+               }
+       }
 }