* RRG_REMOVE_ADMIN_OPTION indicates a grant that would need to have
  * admin_option set to false by the operation.
  *
+ * RRG_REMOVE_INHERIT_OPTION indicates a grant that would need to have
+ * inherit_option set to false by the operation.
+ *
  * RRG_DELETE_GRANT indicates a grant that would need to be removed entirely
  * by the operation.
  */
 {
    RRG_NOOP,
    RRG_REMOVE_ADMIN_OPTION,
+   RRG_REMOVE_INHERIT_OPTION,
    RRG_DELETE_GRANT
 } RevokeRoleGrantAction;
 
 /* Potentially set by pg_upgrade_support functions */
 Oid            binary_upgrade_next_pg_authid_oid = InvalidOid;
 
+typedef struct
+{
+   unsigned    specified;
+   bool        admin;
+   bool        inherit;
+} GrantRoleOptions;
+
+#define GRANT_ROLE_SPECIFIED_ADMIN         0x0001
+#define GRANT_ROLE_SPECIFIED_INHERIT       0x0002
 
 /* GUC parameter */
 int            Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
 
 static void AddRoleMems(const char *rolename, Oid roleid,
                        List *memberSpecs, List *memberIds,
-                       Oid grantorId, bool admin_opt);
+                       Oid grantorId, GrantRoleOptions *popt);
 static void DelRoleMems(const char *rolename, Oid roleid,
                        List *memberSpecs, List *memberIds,
-                       Oid grantorId, bool admin_opt, DropBehavior behavior);
+                       Oid grantorId, GrantRoleOptions *popt,
+                       DropBehavior behavior);
 static Oid check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId,
                               bool is_grant);
 static RevokeRoleGrantAction *initialize_revoke_actions(CatCList *memlist);
 static bool plan_single_revoke(CatCList *memlist,
                               RevokeRoleGrantAction *actions,
                               Oid member, Oid grantor,
-                              bool revoke_admin_option_only,
+                              GrantRoleOptions *popt,
                               DropBehavior behavior);
 static void plan_member_revoke(CatCList *memlist,
                               RevokeRoleGrantAction *actions, Oid member);
                                  int index,
                                  bool revoke_admin_option_only,
                                  DropBehavior behavior);
+static void InitGrantRoleOptions(GrantRoleOptions *popt);
 
 
 /* Check if current user has createrole privileges */
    DefElem    *dadminmembers = NULL;
    DefElem    *dvalidUntil = NULL;
    DefElem    *dbypassRLS = NULL;
+   GrantRoleOptions    popt;
 
    /* The defaults can vary depending on the original statement type */
    switch (stmt->stmt_type)
    if (addroleto || adminmembers || rolemembers)
        CommandCounterIncrement();
 
+   /* Default grant. */
+   InitGrantRoleOptions(&popt);
+
    /*
     * Add the new role to the specified existing roles.
     */
            AddRoleMems(oldrolename, oldroleid,
                        thisrole_list,
                        thisrole_oidlist,
-                       InvalidOid, false);
+                       InvalidOid, &popt);
 
            ReleaseSysCache(oldroletup);
        }
     * Add the specified members to this new role. adminmembers get the admin
     * option, rolemembers don't.
     */
-   AddRoleMems(stmt->role, roleid,
-               adminmembers, roleSpecsToIds(adminmembers),
-               InvalidOid, true);
    AddRoleMems(stmt->role, roleid,
                rolemembers, roleSpecsToIds(rolemembers),
-               InvalidOid, false);
+               InvalidOid, &popt);
+   popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+   popt.admin = true;
+   AddRoleMems(stmt->role, roleid,
+               adminmembers, roleSpecsToIds(adminmembers),
+               InvalidOid, &popt);
 
    /* Post creation hook for new role */
    InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0);
    DefElem    *dvalidUntil = NULL;
    DefElem    *dbypassRLS = NULL;
    Oid         roleid;
+   GrantRoleOptions    popt;
 
    check_rolespec_name(stmt->role,
                        "Cannot alter reserved roles.");
    ReleaseSysCache(tuple);
    heap_freetuple(new_tuple);
 
+   InitGrantRoleOptions(&popt);
+
    /*
     * Advance command counter so we can see new record; else tests in
     * AddRoleMems may fail.
        if (stmt->action == +1) /* add members to role */
            AddRoleMems(rolename, roleid,
                        rolemembers, roleSpecsToIds(rolemembers),
-                       InvalidOid, false);
+                       InvalidOid, &popt);
        else if (stmt->action == -1)    /* drop members from role */
            DelRoleMems(rolename, roleid,
                        rolemembers, roleSpecsToIds(rolemembers),
-                       InvalidOid, false, DROP_RESTRICT);
+                       InvalidOid, &popt, DROP_RESTRICT);
    }
 
    /*
  * Grant/Revoke roles to/from roles
  */
 void
-GrantRole(GrantRoleStmt *stmt)
+GrantRole(ParseState *pstate, GrantRoleStmt *stmt)
 {
    Relation    pg_authid_rel;
    Oid         grantor;
    List       *grantee_ids;
    ListCell   *item;
+   GrantRoleOptions    popt;
+
+   /* Parse options list. */
+   InitGrantRoleOptions(&popt);
+   foreach(item, stmt->opt)
+   {
+       DefElem    *opt = (DefElem *) lfirst(item);
+       char       *optval = defGetString(opt);
+
+       if (strcmp(opt->defname, "admin") == 0)
+       {
+           popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+
+           if (parse_bool(optval, &popt.admin))
+               continue;
+       }
+       else if (strcmp(opt->defname, "inherit") == 0)
+       {
+           popt.specified |= GRANT_ROLE_SPECIFIED_INHERIT;
+           if (parse_bool(optval, &popt.inherit))
+               continue;
+       }
+       else
+           ereport(ERROR,
+                   errcode(ERRCODE_SYNTAX_ERROR),
+                   errmsg("unrecognized role option \"%s\"", opt->defname),
+                   parser_errposition(pstate, opt->location));
+
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("unrecognized value for role option \"%s\": \"%s\"",
+                       opt->defname, optval),
+                parser_errposition(pstate, opt->location)));
+   }
 
+   /* Lookup OID of grantor, if specified. */
    if (stmt->grantor)
        grantor = get_rolespec_oid(stmt->grantor, false);
    else
    pg_authid_rel = table_open(AuthIdRelationId, AccessShareLock);
 
    /*
-    * Step through all of the granted roles and add/remove entries for the
-    * grantees, or, if admin_opt is set, then just add/remove the admin
-    * option.
-    *
-    * Note: Permissions checking is done by AddRoleMems/DelRoleMems
+    * Step through all of the granted roles and add, update, or remove
+    * entries in pg_auth_members as appropriate. If stmt->is_grant is true,
+    * we are adding new grants or, if they already exist, updating options
+    * on those grants. If stmt->is_grant is false, we are revoking grants or
+    * removing options from them.
     */
    foreach(item, stmt->granted_roles)
    {
        if (stmt->is_grant)
            AddRoleMems(rolename, roleid,
                        stmt->grantee_roles, grantee_ids,
-                       grantor, stmt->admin_opt);
+                       grantor, &popt);
        else
            DelRoleMems(rolename, roleid,
                        stmt->grantee_roles, grantee_ids,
-                       grantor, stmt->admin_opt, stmt->behavior);
+                       grantor, &popt, stmt->behavior);
    }
 
    /*
  * memberSpecs: list of RoleSpec of roles to add (used only for error messages)
  * memberIds: OIDs of roles to add
  * grantorId: who is granting the membership (InvalidOid if not set explicitly)
- * admin_opt: granting admin option?
+ * popt: information about grant options
  */
 static void
 AddRoleMems(const char *rolename, Oid roleid,
            List *memberSpecs, List *memberIds,
-           Oid grantorId, bool admin_opt)
+           Oid grantorId, GrantRoleOptions *popt)
 {
    Relation    pg_authmem_rel;
    TupleDesc   pg_authmem_dsc;
     * has no other source of ADMIN OPTION on X, tries to give ADMIN OPTION on
     * X back to A).
     */
-   if (admin_opt && grantorId != BOOTSTRAP_SUPERUSERID)
+   if (popt->admin && grantorId != BOOTSTRAP_SUPERUSERID)
    {
        CatCList   *memlist;
        RevokeRoleGrantAction *actions;
        Datum       new_record[Natts_pg_auth_members] = {0};
        bool        new_record_nulls[Natts_pg_auth_members] = {0};
        bool        new_record_repl[Natts_pg_auth_members] = {0};
-       Form_pg_auth_members authmem_form;
 
-       /*
-        * Check if entry for this role/member already exists; if so, give
-        * warning unless we are adding admin option.
-        */
+       /* Common initialization for possible insert or update */
+       new_record[Anum_pg_auth_members_roleid - 1] =
+           ObjectIdGetDatum(roleid);
+       new_record[Anum_pg_auth_members_member - 1] =
+           ObjectIdGetDatum(memberid);
+       new_record[Anum_pg_auth_members_grantor - 1] =
+           ObjectIdGetDatum(grantorId);
+
+       /* Find any existing tuple */
        authmem_tuple = SearchSysCache3(AUTHMEMROLEMEM,
                                        ObjectIdGetDatum(roleid),
                                        ObjectIdGetDatum(memberid),
                                        ObjectIdGetDatum(grantorId));
-       if (!HeapTupleIsValid(authmem_tuple))
-       {
-           authmem_form = NULL;
-       }
-       else
+
+       /*
+        * If we found a tuple, update it with new option values, unless
+        * there are no changes, in which case issue a WARNING.
+        *
+        * If we didn't find a tuple, just insert one.
+        */
+       if (HeapTupleIsValid(authmem_tuple))
        {
+           Form_pg_auth_members authmem_form;
+           bool        at_least_one_change = false;
+
            authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
 
-           if (!admin_opt || authmem_form->admin_option)
+           if ((popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0
+               && authmem_form->admin_option != popt->admin)
+           {
+               new_record[Anum_pg_auth_members_admin_option - 1] =
+                   BoolGetDatum(popt->admin);
+               new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+                   true;
+               at_least_one_change = true;
+           }
+
+           if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0
+               && authmem_form->inherit_option != popt->inherit)
+           {
+               new_record[Anum_pg_auth_members_inherit_option - 1] =
+                   BoolGetDatum(popt->inherit);
+               new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+                   true;
+               at_least_one_change = true;
+           }
+
+           if (!at_least_one_change)
            {
                ereport(NOTICE,
                        (errmsg("role \"%s\" has already been granted membership in role \"%s\" by role \"%s\"",
                ReleaseSysCache(authmem_tuple);
                continue;
            }
-       }
 
-       /* Build a tuple to insert or update */
-       new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);
-       new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid);
-       new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId);
-       new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
-
-       if (HeapTupleIsValid(authmem_tuple))
-       {
-           new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
            tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
                                      new_record,
                                      new_record_nulls, new_record_repl);
            Oid         objectId;
            Oid        *newmembers = palloc(sizeof(Oid));
 
+           /* Set admin option if user set it to true, otherwise not. */
+           new_record[Anum_pg_auth_members_admin_option - 1] =
+               BoolGetDatum(popt->admin);
+
+           /*
+            * If the user specified a value for the inherit option, use
+            * whatever was specified. Otherwise, set the default value based
+            * on the role-level property.
+            */
+           if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0)
+               new_record[Anum_pg_auth_members_inherit_option - 1] =
+                   popt->inherit;
+           else
+           {
+               HeapTuple       mrtup;
+               Form_pg_authid  mrform;
+
+               mrtup = SearchSysCache1(AUTHOID, memberid);
+               if (!HeapTupleIsValid(mrtup))
+                   elog(ERROR, "cache lookup failed for role %u", memberid);
+               mrform = (Form_pg_authid) GETSTRUCT(mrtup);
+               new_record[Anum_pg_auth_members_inherit_option - 1] =
+                   mrform->rolinherit;
+               ReleaseSysCache(mrtup);
+           }
+
+           /* get an OID for the new row and insert it */
            objectId = GetNewObjectId();
            new_record[Anum_pg_auth_members_oid - 1] = objectId;
            tuple = heap_form_tuple(pg_authmem_dsc,
  * memberSpecs: list of RoleSpec of roles to del (used only for error messages)
  * memberIds: OIDs of roles to del
  * grantorId: who is revoking the membership
- * admin_opt: remove admin option only?
+ * popt: information about grant options
  * behavior: RESTRICT or CASCADE behavior for recursive removal
  */
 static void
 DelRoleMems(const char *rolename, Oid roleid,
            List *memberSpecs, List *memberIds,
-           Oid grantorId, bool admin_opt, DropBehavior behavior)
+           Oid grantorId, GrantRoleOptions *popt, DropBehavior behavior)
 {
    Relation    pg_authmem_rel;
    TupleDesc   pg_authmem_dsc;
        Oid         memberid = lfirst_oid(iditem);
 
        if (!plan_single_revoke(memlist, actions, memberid, grantorId,
-                               admin_opt, behavior))
+                               popt, behavior))
        {
            ereport(WARNING,
                    (errmsg("role \"%s\" has not been granted membership in role \"%s\" by role \"%s\"",
        }
        else
        {
-           /* Just turn off the admin option */
+           /* Just turn off the specified option */
            HeapTuple   tuple;
            Datum       new_record[Natts_pg_auth_members] = {0};
            bool        new_record_nulls[Natts_pg_auth_members] = {0};
            bool        new_record_repl[Natts_pg_auth_members] = {0};
 
            /* Build a tuple to update with */
-           new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false);
-           new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+           if (actions[i] == RRG_REMOVE_ADMIN_OPTION)
+           {
+               new_record[Anum_pg_auth_members_admin_option - 1] =
+                   BoolGetDatum(false);
+               new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+                   true;
+           }
+           else if (actions[i] == RRG_REMOVE_INHERIT_OPTION)
+           {
+               new_record[Anum_pg_auth_members_inherit_option - 1] =
+                   BoolGetDatum(false);
+               new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+                   true;
+           }
+           else
+               elog(ERROR, "unknown role revoke action");
 
            tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
                                      new_record,
  */
 static bool
 plan_single_revoke(CatCList *memlist, RevokeRoleGrantAction *actions,
-                  Oid member, Oid grantor, bool revoke_admin_option_only,
+                  Oid member, Oid grantor, GrantRoleOptions *popt,
                   DropBehavior behavior)
 {
    int         i;
 
+   /*
+    * If popt.specified == 0, we're revoking the grant entirely; otherwise,
+    * we expect just one bit to be set, and we're revoking the corresponding
+    * option. As of this writing, there's no syntax that would allow for
+    * an attempt to revoke multiple options at once, and the logic below
+    * wouldn't work properly if such syntax were added, so assert that our
+    * caller isn't trying to do that.
+    */
+   Assert(pg_popcount32(popt->specified) <= 1);
+
    for (i = 0; i < memlist->n_members; ++i)
    {
        HeapTuple   authmem_tuple;
        if (authmem_form->member == member &&
            authmem_form->grantor == grantor)
        {
-           plan_recursive_revoke(memlist, actions, i,
-                                 revoke_admin_option_only, behavior);
+           if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0)
+           {
+               /*
+                * Revoking the INHERIT option doesn't change anything for
+                * dependent privileges, so we don't need to recurse.
+                */
+               actions[i] = RRG_REMOVE_INHERIT_OPTION;
+           }
+           else
+           {
+               bool    revoke_admin_option_only;
+
+               /*
+                * Revoking the grant entirely, or ADMIN option on a grant,
+                * implicates dependent privileges, so we may need to recurse.
+                */
+               revoke_admin_option_only =
+                   (popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0;
+               plan_recursive_revoke(memlist, actions, i,
+                                     revoke_admin_option_only, behavior);
+           }
            return true;
        }
    }
        }
    }
 }
+
+/*
+ * Initialize a GrantRoleOptions object with default values.
+ */
+static void
+InitGrantRoleOptions(GrantRoleOptions *popt)
+{
+   popt->specified = 0;
+   popt->admin = false;
+   popt->inherit = false;
+}
 
  */
 enum RoleRecurseType
 {
-   ROLERECURSE_PRIVS = 0,      /* recurse if rolinherit */
+   ROLERECURSE_PRIVS = 0,      /* recurse through inheritable grants */
    ROLERECURSE_MEMBERS = 1     /* recurse unconditionally */
 };
 static Oid cached_role[] = {InvalidOid, InvalidOid};
 
        /*
         * In normal mode, set a callback on any syscache invalidation of rows
-        * of pg_auth_members (for roles_is_member_of()), pg_authid (for
-        * has_rolinherit()), or pg_database (for roles_is_member_of())
+        * of pg_auth_members (for roles_is_member_of()) pg_database (for
+        * roles_is_member_of())
         */
        CacheRegisterSyscacheCallback(AUTHMEMROLEMEM,
                                      RoleMembershipCacheCallback,
    cached_role[ROLERECURSE_MEMBERS] = InvalidOid;
 }
 
-
-/* Check if specified role has rolinherit set */
-static bool
-has_rolinherit(Oid roleid)
-{
-   bool        result = false;
-   HeapTuple   utup;
-
-   utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
-   if (HeapTupleIsValid(utup))
-   {
-       result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
-       ReleaseSysCache(utup);
-   }
-   return result;
-}
-
-
 /*
  * Get a list of roles that the specified roleid is a member of
  *
- * Type ROLERECURSE_PRIVS recurses only through roles that have rolinherit
- * set, while ROLERECURSE_MEMBERS recurses through all roles.
+ * Type ROLERECURSE_PRIVS recurses only through inheritable grants,
+ * while ROLERECURSE_MEMBERS recurses through all grants.
  *
  * Since indirect membership testing is relatively expensive, we cache
  * a list of memberships.  Hence, the result is only guaranteed good until
        CatCList   *memlist;
        int         i;
 
-       if (type == ROLERECURSE_PRIVS && !has_rolinherit(memberid))
-           continue;           /* ignore non-inheriting roles */
-
        /* Find roles that memberid is directly a member of */
        memlist = SearchSysCacheList1(AUTHMEMMEMROLE,
                                      ObjectIdGetDatum(memberid));
        for (i = 0; i < memlist->n_members; i++)
        {
            HeapTuple   tup = &memlist->members[i]->tuple;
-           Oid         otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
+           Form_pg_auth_members form = (Form_pg_auth_members) GETSTRUCT(tup);
+           Oid         otherid = form->roleid;
+
+           /* If we're supposed to ignore non-heritable grants, do so. */
+           if (type == ROLERECURSE_PRIVS && !form->inherit_option)
+               continue;
 
            /*
             * While otherid==InvalidOid shouldn't appear in the catalog, the
             * OidIsValid() avoids crashing if that arises.
             */
-           if (otherid == admin_of &&
-               ((Form_pg_auth_members) GETSTRUCT(tup))->admin_option &&
+           if (otherid == admin_of && form->admin_option &&
                OidIsValid(admin_of) && !OidIsValid(*admin_role))
                *admin_role = memberid;
 
 /*
  * 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
+ * This is defined not to recurse through grants that are not inherited;
+ * in such cases, membership implies the ability to do SET ROLE, but
  * the privileges are not available until you've done so.
  */
 bool
 /*
  * Is member a member of role (directly or indirectly)?
  *
- * This is defined to recurse through roles regardless of rolinherit.
+ * This is defined to recurse through grants whether they are inherited or not.
  *
  * Do not use this for privilege checking, instead use has_privs_of_role()
  */