diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c5d75c4c6578827e24dd4f0a71b9455ce7150f33..fe14a6042634f032aad01b003b968407b4adc48a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13588,10 +13588,10 @@ SELECT typlen FROM pg_type WHERE oid = pg_typeof(33);
    </table>
 
    <para>
-    <function>col_description</function> returns the comment for a table column,
-    which is specified by the OID of its table and its column number.
-    <function>obj_description</function> cannot be used for table columns since
-    columns do not have OIDs of their own.
+    <function>col_description</function> returns the comment for a table
+    column, which is specified by the OID of its table and its column number.
+    (<function>obj_description</function> cannot be used for table columns
+    since columns do not have OIDs of their own.)
    </para>
 
    <para>
@@ -13610,8 +13610,8 @@ SELECT typlen FROM pg_type WHERE oid = pg_typeof(33);
     <function>shobj_description</function> is used just like
     <function>obj_description</function> except it is used for retrieving
     comments on shared objects.  Some system catalogs are global to all
-    databases within each cluster and their descriptions are stored globally
-    as well.
+    databases within each cluster, and the descriptions for objects in them
+    are stored globally as well.
    </para>
 
    <indexterm>
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index 2610fd5b8d505bb0baaa414d3b14476404daaebf..bc848b306999abe7ebc1a917fdb52104c3adc9c7 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -65,11 +65,18 @@ COMMENT ON
   </para>
 
   <para>
-    To modify a comment, issue a new <command>COMMENT</> command for the
-    same object.  Only one comment string is stored for each object.
-    To remove a comment, write <literal>NULL</literal> in place of the text
-    string.
-    Comments are automatically dropped when the object is dropped.
+   Only one comment string is stored for each object, so to modify a comment,
+   issue a new <command>COMMENT</> command for the same object.  To remove a
+   comment, write <literal>NULL</literal> in place of the text string.
+   Comments are automatically dropped when their object is dropped.
+  </para>
+
+  <para>
+   For most kinds of object, only the object's owner can set the comment.
+   Roles don't have owners, so the rule for <literal>COMMENT ON ROLE</> is
+   that you must be superuser to comment on a superuser role, or have the
+   <literal>CREATEROLE</> privilege to comment on non-superuser roles.
+   Of course, a superuser can comment on anything.
   </para>
 
   <para>
@@ -93,15 +100,15 @@ COMMENT ON
     <term><replaceable class="parameter">agg_name</replaceable></term>
     <term><replaceable class="parameter">constraint_name</replaceable></term>
     <term><replaceable class="parameter">function_name</replaceable></term>
-    <term><replaceable class="parameter">op</replaceable></term>
+    <term><replaceable class="parameter">operator_name</replaceable></term>
     <term><replaceable class="parameter">rule_name</replaceable></term>
     <term><replaceable class="parameter">trigger_name</replaceable></term>
     <listitem>
      <para>
       The name of the object to be commented.  Names of tables,
-      aggregates, domains, foreign tables, functions, indexes, operators,
-      operator classes, operator families, sequences, text search objects,
-      types, and views can be schema-qualified.
+      aggregates, collations, conversions, domains, foreign tables, functions,
+      indexes, operators, operator classes, operator families, sequences,
+      text search objects, types, and views can be schema-qualified.
      </para>
     </listitem>
    </varlistentry>
@@ -137,7 +144,6 @@ COMMENT ON
 
    <varlistentry>
     <term><replaceable class="parameter">argmode</replaceable></term>
-
     <listitem>
      <para>
       The mode of a function argument: <literal>IN</>, <literal>OUT</>,
@@ -154,7 +160,6 @@ COMMENT ON
 
    <varlistentry>
     <term><replaceable class="parameter">argname</replaceable></term>
-
     <listitem>
      <para>
       The name of a function argument.
@@ -167,7 +172,6 @@ COMMENT ON
 
    <varlistentry>
     <term><replaceable class="parameter">argtype</replaceable></term>
-
     <listitem>
      <para>
       The data type(s) of the function's arguments (optionally
@@ -185,9 +189,20 @@ COMMENT ON
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">left_type</replaceable></term>
+    <term><replaceable class="parameter">right_type</replaceable></term>
+    <listitem>
+     <para>
+      The data type(s) of the operator's arguments (optionally
+      schema-qualified).  Write <literal>NONE</> for the missing argument
+      of a prefix or postfix operator.
+     </para>
+    </listitem>
+   </varlistentry>
+
     <varlistentry>
      <term><literal>PROCEDURAL</literal></term>
-
      <listitem>
       <para>
        This is a noise word.
@@ -212,12 +227,11 @@ COMMENT ON
   <title>Notes</title>
 
   <para>
-   There is presently no security mechanism for comments: any user
+   There is presently no security mechanism for viewing comments: any user
    connected to a database can see all the comments for objects in
-   that database (although only superusers can change comments for
-   objects that they don't own).  For shared objects such as
-   databases, roles, and tablespaces comments are stored globally
-   and any user connected to any database can see all the comments
+   that database.  For shared objects such as
+   databases, roles, and tablespaces, comments are stored globally so any
+   user connected to any database in the cluster can see all the comments
    for shared objects.  Therefore, don't put security-critical
    information in comments.
   </para>
@@ -257,7 +271,7 @@ COMMENT ON INDEX my_index IS 'Enforces uniqueness on employee ID';
 COMMENT ON LANGUAGE plpython IS 'Python support for stored procedures';
 COMMENT ON LARGE OBJECT 346344 IS 'Planning document';
 COMMENT ON OPERATOR ^ (text, text) IS 'Performs intersection of two texts';
-COMMENT ON OPERATOR - (NONE, text) IS 'This is a prefix operator on text';
+COMMENT ON OPERATOR - (NONE, integer) IS 'Unary minus';
 COMMENT ON OPERATOR CLASS int4ops USING btree IS '4 byte integer operators for btrees';
 COMMENT ON OPERATOR FAMILY integer_ops USING btree IS 'all integer operators for btrees';
 COMMENT ON ROLE my_role IS 'Administration group for finance tables';
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index a98f918a239dda9e413ee7a33cc15b126cc44b77..48fa6d48b7f49817ff3d87dc6fdddc818708bb19 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -4735,6 +4735,36 @@ pg_extension_ownercheck(Oid ext_oid, Oid roleid)
 	return has_privs_of_role(roleid, ownerId);
 }
 
+/*
+ * Check whether specified role has CREATEROLE privilege (or is a superuser)
+ *
+ * Note: roles do not have owners per se; instead we use this test in
+ * places where an ownership-like permissions test is needed for a role.
+ * Be sure to apply it to the role trying to do the operation, not the
+ * role being operated on!  Also note that this generally should not be
+ * considered enough privilege if the target role is a superuser.
+ * (We don't handle that consideration here because we want to give a
+ * separate error message for such cases, so the caller has to deal with it.)
+ */
+bool
+has_createrole_privilege(Oid roleid)
+{
+	bool		result = false;
+	HeapTuple	utup;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return true;
+
+	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
+	if (HeapTupleIsValid(utup))
+	{
+		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole;
+		ReleaseSysCache(utup);
+	}
+	return result;
+}
+
 /*
  * Fetch pg_default_acl entry for given role, namespace and object type
  * (object type must be given in pg_default_acl's encoding).
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index b8b89ab7c19d4247141e8daf42d2b777f46e62d5..880b95df0200905ec428de0a3333a6ae7414372c 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -808,13 +808,6 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 				aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE,
 							   NameListToString(objname));
 			break;
-		case OBJECT_ROLE:
-			if (!has_privs_of_role(roleid, address.objectId))
-				ereport(ERROR,
-						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-						 errmsg("must be member of role \"%s\"",
-								NameListToString(objname))));
-			break;
 		case OBJECT_TSDICTIONARY:
 			if (!pg_ts_dict_ownercheck(address.objectId, roleid))
 				aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
@@ -825,6 +818,26 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 				aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
 							   NameListToString(objname));
 			break;
+		case OBJECT_ROLE:
+			/*
+			 * We treat roles as being "owned" by those with CREATEROLE priv,
+			 * except that superusers are only owned by superusers.
+			 */
+			if (superuser_arg(address.objectId))
+			{
+				if (!superuser_arg(roleid))
+					ereport(ERROR,
+							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+							 errmsg("must be superuser")));
+			}
+			else
+			{
+				if (!has_createrole_privilege(roleid))
+					ereport(ERROR,
+							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+							 errmsg("must have CREATEROLE privilege")));
+			}
+			break;
 		case OBJECT_FDW:
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 63f22d8adc204bbd6883492f73ac4f9b1884bfc8..f13eb2891e2b5921f4b0dc35cef5efd60451eedb 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -58,20 +58,7 @@ static void DelRoleMems(const char *rolename, Oid roleid,
 static bool
 have_createrole_privilege(void)
 {
-	bool		result = false;
-	HeapTuple	utup;
-
-	/* Superusers can always do everything */
-	if (superuser())
-		return true;
-
-	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId()));
-	if (HeapTupleIsValid(utup))
-	{
-		result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole;
-		ReleaseSysCache(utup);
-	}
-	return result;
+	return has_createrole_privilege(GetUserId());
 }
 
 
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index c0f7b64d80694efa081cf2a97b52803fc61ea79a..e96323efcc7e335c7797b28a1a4b43e86afcbc7b 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -317,5 +317,6 @@ extern bool pg_ts_dict_ownercheck(Oid dict_oid, Oid roleid);
 extern bool pg_ts_config_ownercheck(Oid cfg_oid, Oid roleid);
 extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
 extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
+extern bool has_createrole_privilege(Oid roleid);
 
 #endif   /* ACL_H */