diff --git a/doc/src/sgml/ref/alter_operator.sgml b/doc/src/sgml/ref/alter_operator.sgml
index a4a1af564f..ce447e22b0 100644
--- a/doc/src/sgml/ref/alter_operator.sgml
+++ b/doc/src/sgml/ref/alter_operator.sgml
@@ -29,8 +29,12 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
 
 ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</replaceable> | NONE } , <replaceable>right_type</replaceable> )
     SET ( {  RESTRICT = { <replaceable class="parameter">res_proc</replaceable> | NONE }
-           | JOIN = { <replaceable class="parameter">join_proc</replaceable> | NONE }
-         } [, ... ] )
+            | JOIN = { <replaceable class="parameter">join_proc</replaceable> | NONE }
+            | COMMUTATOR = <replaceable class="parameter">com_op</replaceable> 
+            | NEGATOR = <replaceable class="parameter">neg_op</replaceable>
+            | HASHES
+            | MERGES
+          } [, ... ] )
 </synopsis>
  </refsynopsisdiv>
 
@@ -121,9 +125,64 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
      </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">com_op</replaceable></term>
+    <listitem>
+     <para>
+      The commutator of this operator. Can only be set if the operator does not have an existing commutator.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">neg_op</replaceable></term>
+    <listitem>
+     <para>
+      The negator of this operator. Can only be set if the operator does not have an existing negator.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>HASHES</literal></term>
+    <listitem>
+     <para>
+     Indicates this operator can support a hash join. Can only be enabled and not disabled.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>MERGES</literal></term>
+    <listitem>
+     <para>
+     Indicates this operator can support a merge join. Can only be enabled and not disabled.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
  </refsect1>
 
+  <refsect1>
+  <title>Notes</title>
+
+  <para>
+   Refer to <xref linkend="xoper"/> and <xref linkend="xoper-optimization"/> for further information.
+  </para>
+
+  <para>
+  Since commutators come in pairs and are commutators of each other,
+  <productname>PostgreSQL</productname> will set the commutator of the
+  <replaceable class="parameter">com_op</replaceable> to the operator.
+  </para>
+
+  <para>
+  Like with commutators, <productname>PostgreSQL</productname> will set the
+  negator of the <replaceable class="parameter">neg_op</replaceable> to the operator.
+  </para>
+  </refsect1>
+
  <refsect1>
   <title>Examples</title>
 
diff --git a/doc/src/sgml/ref/create_operator.sgml b/doc/src/sgml/ref/create_operator.sgml
index e27512ff39..9801eb3ed0 100644
--- a/doc/src/sgml/ref/create_operator.sgml
+++ b/doc/src/sgml/ref/create_operator.sgml
@@ -231,9 +231,60 @@ COMMUTATOR = OPERATOR(myschema.===) ,
   <title>Notes</title>
 
   <para>
-   Refer to <xref linkend="xoper"/> for further information.
+   Refer to <xref linkend="xoper"/> and <xref linkend="xoper-optimization"/> for further information.
   </para>
 
+  <para>
+    When you are defining a self-commutative operator, you just do it.
+    When you are defining a pair of commutative operators, things are
+    a little trickier: how can the first one to be defined refer to the
+    other one, which you haven't defined yet?  There are three solutions
+    to this problem:
+
+    <itemizedlist>
+     <listitem>
+      <para>
+       One way is to omit the <literal>COMMUTATOR</literal> clause in the first operator that
+       you define, and then provide one in the second operator's definition.
+       Since <productname>PostgreSQL</productname> knows that commutative
+       operators come in pairs, when it sees the second definition it will
+       automatically go back and fill in the missing <literal>COMMUTATOR</literal> clause in
+       the first definition.
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       Another, more straightforward way is just to include <literal>COMMUTATOR</literal> clauses
+       in both definitions.  When <productname>PostgreSQL</productname> processes
+       the first definition and realizes that <literal>COMMUTATOR</literal> refers to a nonexistent
+       operator, the system will make a dummy entry for that operator in the
+       system catalog.  This dummy entry will have valid data only
+       for the operator name, left and right operand types, and result type,
+       since that's all that <productname>PostgreSQL</productname> can deduce
+       at this point.  The first operator's catalog entry will link to this
+       dummy entry.  Later, when you define the second operator, the system
+       updates the dummy entry with the additional information from the second
+       definition.  If you try to use the dummy operator before it's been filled
+       in, you'll just get an error message.
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+      Alternatively, both operators can be defined without a <literal>COMMUTATOR</literal>
+      and then <command>ALTER OPERATOR</command> can be used to set the <literal>COMMUTATOR</literal>
+      for the operator.
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    Pairs of negator operators can be defined using the same methods
+    explained above for commutator pairs.
+   </para>
+
   <para>
    It is not possible to specify an operator's lexical precedence in
    <command>CREATE OPERATOR</command>, because the parser's precedence behavior
diff --git a/doc/src/sgml/xoper.sgml b/doc/src/sgml/xoper.sgml
index a929ced07d..31bc8df3de 100644
--- a/doc/src/sgml/xoper.sgml
+++ b/doc/src/sgml/xoper.sgml
@@ -146,44 +146,6 @@ SELECT (a + b) AS c FROM test_complex;
      <literal>=</literal> operator must specify that it is valid, by marking the
      operator with commutator information.
     </para>
-
-    <para>
-     When you are defining a self-commutative operator, you just do it.
-     When you are defining a pair of commutative operators, things are
-     a little trickier: how can the first one to be defined refer to the
-     other one, which you haven't defined yet?  There are two solutions
-     to this problem:
-
-     <itemizedlist>
-      <listitem>
-       <para>
-        One way is to omit the <literal>COMMUTATOR</literal> clause in the first operator that
-        you define, and then provide one in the second operator's definition.
-        Since <productname>PostgreSQL</productname> knows that commutative
-        operators come in pairs, when it sees the second definition it will
-        automatically go back and fill in the missing <literal>COMMUTATOR</literal> clause in
-        the first definition.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        The other, more straightforward way is just to include <literal>COMMUTATOR</literal> clauses
-        in both definitions.  When <productname>PostgreSQL</productname> processes
-        the first definition and realizes that <literal>COMMUTATOR</literal> refers to a nonexistent
-        operator, the system will make a dummy entry for that operator in the
-        system catalog.  This dummy entry will have valid data only
-        for the operator name, left and right operand types, and result type,
-        since that's all that <productname>PostgreSQL</productname> can deduce
-        at this point.  The first operator's catalog entry will link to this
-        dummy entry.  Later, when you define the second operator, the system
-        updates the dummy entry with the additional information from the second
-        definition.  If you try to use the dummy operator before it's been filled
-        in, you'll just get an error message.
-       </para>
-      </listitem>
-     </itemizedlist>
-    </para>
    </sect2>
 
    <sect2 id="xoper-negator">
@@ -218,12 +180,7 @@ SELECT (a + b) AS c FROM test_complex;
     <literal>NOT</literal> operations can be inserted as a consequence of other rearrangements.
    </para>
 
-   <para>
-    Pairs of negator operators can be defined using the same methods
-    explained above for commutator pairs.
-   </para>
-
-  </sect2>
+   </sect2>
 
   <sect2 id="xoper-restrict">
    <title><literal>RESTRICT</literal></title>
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index b75f7a9bc1..578c89b6f9 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -44,11 +44,6 @@ static Oid	OperatorGet(const char *operatorName,
 						Oid rightObjectId,
 						bool *defined);
 
-static Oid	OperatorLookup(List *operatorName,
-						   Oid leftObjectId,
-						   Oid rightObjectId,
-						   bool *defined);
-
 static Oid	OperatorShellMake(const char *operatorName,
 							  Oid operatorNamespace,
 							  Oid leftTypeId,
@@ -165,7 +160,7 @@ OperatorGet(const char *operatorName,
  *
  *		*defined is set true if defined (not a shell)
  */
-static Oid
+Oid
 OperatorLookup(List *operatorName,
 			   Oid leftObjectId,
 			   Oid rightObjectId,
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index cd7f83136f..6666bf476f 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -54,6 +54,9 @@
 
 static Oid	ValidateRestrictionEstimator(List *restrictionName);
 static Oid	ValidateJoinEstimator(List *joinName);
+static Oid	ValidateOperatorDefined(List *name,
+									Oid leftTypeId,
+									Oid rightTypeId);
 
 /*
  * DefineOperator
@@ -359,6 +362,44 @@ ValidateJoinEstimator(List *joinName)
 	return joinOid;
 }
 
+/*
+ * Looks up and returns an oid for an operator given a possibly-qualified name
+ * and left and right type IDs, verifies the operator is defined (non-shell),
+ * and owned by the current user. Raises an error otherwise.
+ */
+static Oid
+ValidateOperatorDefined(List *name,
+						Oid leftTypeId,
+						Oid rightTypeId)
+{
+	Oid			oid;
+	bool		defined;
+
+	oid = OperatorLookup(name,
+						 leftTypeId,
+						 rightTypeId,
+						 &defined);
+
+	if (!OidIsValid(oid))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("operator %s does not exist",
+						NameListToString(name))));
+
+	if (!defined)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("operator %s is undefined shell operator",
+						NameListToString(name))));
+
+	if (!object_ownercheck(OperatorRelationId, oid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR,
+					   NameListToString(name));
+
+	return oid;
+}
+
+
 /*
  * Guts of operator deletion.
  */
@@ -406,6 +447,8 @@ RemoveOperatorById(Oid operOid)
  *		routine implementing ALTER OPERATOR <operator> SET (option = ...).
  *
  * Currently, only RESTRICT and JOIN estimator functions can be changed.
+ * COMMUTATOR and NEGATOR, HASHES, and MERGES can be set if they have not been set
+ * previously.
  */
 ObjectAddress
 AlterOperator(AlterOperatorStmt *stmt)
@@ -426,6 +469,14 @@ AlterOperator(AlterOperatorStmt *stmt)
 	List	   *joinName = NIL; /* optional join sel. function */
 	bool		updateJoin = false;
 	Oid			joinOid;
+	List	   *commutatorName = NIL;	/* optional commutator operator name */
+	Oid			commutatorOid;
+	List	   *negatorName = NIL;	/* optional negator operator name */
+	Oid			negatorOid;
+	bool		updateHashes = false;
+	bool		canHash = false;
+	bool		updateMerges = false;
+	bool		canMerge = false;
 
 	/* Look up the operator */
 	oprId = LookupOperWithArgs(stmt->opername, false);
@@ -456,6 +507,24 @@ AlterOperator(AlterOperatorStmt *stmt)
 			joinName = param;
 			updateJoin = true;
 		}
+		else if (strcmp(defel->defname, "commutator") == 0)
+		{
+			commutatorName = defGetQualifiedName(defel);
+		}
+		else if (strcmp(defel->defname, "negator") == 0)
+		{
+			negatorName = defGetQualifiedName(defel);
+		}
+		else if (strcmp(defel->defname, "hashes") == 0)
+		{
+			canHash = defGetBoolean(defel);
+			updateHashes = true;
+		}
+		else if (strcmp(defel->defname, "merges") == 0)
+		{
+			canMerge = defGetBoolean(defel);
+			updateMerges = true;
+		}
 
 		/*
 		 * The rest of the options that CREATE accepts cannot be changed.
@@ -464,11 +533,7 @@ AlterOperator(AlterOperatorStmt *stmt)
 		else if (strcmp(defel->defname, "leftarg") == 0 ||
 				 strcmp(defel->defname, "rightarg") == 0 ||
 				 strcmp(defel->defname, "function") == 0 ||
-				 strcmp(defel->defname, "procedure") == 0 ||
-				 strcmp(defel->defname, "commutator") == 0 ||
-				 strcmp(defel->defname, "negator") == 0 ||
-				 strcmp(defel->defname, "hashes") == 0 ||
-				 strcmp(defel->defname, "merges") == 0)
+				 strcmp(defel->defname, "procedure") == 0)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
@@ -488,7 +553,7 @@ AlterOperator(AlterOperatorStmt *stmt)
 					   NameStr(oprForm->oprname));
 
 	/*
-	 * Look up restriction and join estimators if specified
+	 * Look up Oid for any parameters specified
 	 */
 	if (restrictionName)
 		restrictionOid = ValidateRestrictionEstimator(restrictionName);
@@ -499,28 +564,85 @@ AlterOperator(AlterOperatorStmt *stmt)
 	else
 		joinOid = InvalidOid;
 
-	/* Perform additional checks, like OperatorCreate does */
-	if (!(OidIsValid(oprForm->oprleft) && OidIsValid(oprForm->oprright)))
+	if (commutatorName)
 	{
-		/* If it's not a binary op, these things mustn't be set: */
-		if (OidIsValid(joinOid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
-					 errmsg("only binary operators can have join selectivity")));
+		/* commutator has reversed arg types */
+		commutatorOid = ValidateOperatorDefined(commutatorName,
+												oprForm->oprright,
+												oprForm->oprleft);
+
+		/*
+		 * we don't need to do anything extra for a self commutator as in
+		 * OperatorCreate as there we have to create the operator before
+		 * setting the commutator, but here the operator already exists and
+		 * the commutatorOid above is valid (and is the operator oid).
+		 */
 	}
+	else
+		commutatorOid = InvalidOid;
 
-	if (oprForm->oprresult != BOOLOID)
+	if (negatorName)
 	{
-		if (OidIsValid(restrictionOid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
-					 errmsg("only boolean operators can have restriction selectivity")));
-		if (OidIsValid(joinOid))
+		negatorOid = ValidateOperatorDefined(negatorName,
+											 oprForm->oprleft,
+											 oprForm->oprright);
+
+		if (negatorOid == oprForm->oid)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
-					 errmsg("only boolean operators can have join selectivity")));
+					 errmsg("operator cannot be its own negator")));
+	}
+	else
+	{
+		negatorOid = InvalidOid;
+	}
+
+	/*
+	 * check that we're not changing any existing values that might be
+	 * depended on elsewhere and may be expected to never change, while
+	 * allowing no-ops.
+	 */
+	if (OidIsValid(commutatorOid) && OidIsValid(oprForm->oprcom)
+		&& commutatorOid != oprForm->oprcom)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+				 errmsg("operator attribute \"commutator\" cannot be changed if it has already been set")));
+	}
+
+	if (OidIsValid(negatorOid) && OidIsValid(oprForm->oprnegate)
+		&& negatorOid != oprForm->oprnegate)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+				 errmsg("operator attribute \"negator\" cannot be changed if it has already been set")));
+	}
+
+	if (updateHashes && oprForm->oprcanhash && !canHash)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+				 errmsg("operator attribute \"hashes\" cannot be disabled after it has been enabled")));
+	}
+
+	if (updateMerges && oprForm->oprcanmerge && !canMerge)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+				 errmsg("operator attribute \"merges\" cannot be disabled after it has been enabled")));
 	}
 
+	/* Perform additional checks, like OperatorCreate does */
+	OperatorValidateParams(oprForm->oprleft,
+						   oprForm->oprright,
+						   oprForm->oprresult,
+						   OidIsValid(commutatorOid),
+						   OidIsValid(negatorOid),
+						   OidIsValid(joinOid),
+						   OidIsValid(restrictionOid),
+						   updateMerges,
+						   updateHashes);
+
 	/* Update the tuple */
 	for (i = 0; i < Natts_pg_operator; ++i)
 	{
@@ -539,6 +661,30 @@ AlterOperator(AlterOperatorStmt *stmt)
 		values[Anum_pg_operator_oprjoin - 1] = joinOid;
 	}
 
+	if (OidIsValid(commutatorOid))
+	{
+		replaces[Anum_pg_operator_oprcom - 1] = true;
+		values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(commutatorOid);
+	}
+
+	if (OidIsValid(negatorOid))
+	{
+		replaces[Anum_pg_operator_oprnegate - 1] = true;
+		values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(negatorOid);
+	}
+
+	if (updateMerges)
+	{
+		replaces[Anum_pg_operator_oprcanmerge - 1] = true;
+		values[Anum_pg_operator_oprcanmerge - 1] = canMerge;
+	}
+
+	if (updateHashes)
+	{
+		replaces[Anum_pg_operator_oprcanhash - 1] = true;
+		values[Anum_pg_operator_oprcanhash - 1] = canHash;
+	}
+
 	tup = heap_modify_tuple(tup, RelationGetDescr(catalog),
 							values, nulls, replaces);
 
@@ -546,6 +692,9 @@ AlterOperator(AlterOperatorStmt *stmt)
 
 	address = makeOperatorDependencies(tup, false, true);
 
+	if (OidIsValid(commutatorOid) || OidIsValid(negatorOid))
+		OperatorUpd(oprId, commutatorOid, negatorOid, false);
+
 	InvokeObjectPostAlterHook(OperatorRelationId, oprId, 0);
 
 	table_close(catalog, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e56cbe77cb..0c7a1f77e0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10151,6 +10151,8 @@ operator_def_elem: ColLabel '=' NONE
 						{ $$ = makeDefElem($1, NULL, @1); }
 				   | ColLabel '=' operator_def_arg
 						{ $$ = makeDefElem($1, (Node *) $3, @1); }
+				   | ColLabel
+						{ $$ = makeDefElem($1, NULL, @1); }
 		;
 
 /* must be similar enough to def_arg to avoid reduce/reduce conflicts */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index a670973ab2..13ebc592f2 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -85,6 +85,11 @@ typedef FormData_pg_operator *Form_pg_operator;
 DECLARE_UNIQUE_INDEX_PKEY(pg_operator_oid_index, 2688, OperatorOidIndexId, pg_operator, btree(oid oid_ops));
 DECLARE_UNIQUE_INDEX(pg_operator_oprname_l_r_n_index, 2689, OperatorNameNspIndexId, pg_operator, btree(oprname name_ops, oprleft oid_ops, oprright oid_ops, oprnamespace oid_ops));
 
+extern Oid
+			OperatorLookup(List *operatorName,
+						   Oid leftObjectId,
+						   Oid rightObjectId,
+						   bool *defined);
 
 extern ObjectAddress OperatorCreate(const char *operatorName,
 									Oid operatorNamespace,
diff --git a/src/test/regress/expected/alter_operator.out b/src/test/regress/expected/alter_operator.out
index 71bd484282..ea5d79b054 100644
--- a/src/test/regress/expected/alter_operator.out
+++ b/src/test/regress/expected/alter_operator.out
@@ -25,7 +25,7 @@ ORDER BY 1;
 (3 rows)
 
 --
--- Reset and set params
+-- test reset and set restrict and join
 --
 ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
 ALTER OPERATOR === (boolean, boolean) SET (JOIN = NONE);
@@ -106,34 +106,282 @@ ORDER BY 1;
  schema public                                         | n
 (3 rows)
 
---
--- Test invalid options.
---
-ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
-ERROR:  operator attribute "commutator" cannot be changed
-ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
-ERROR:  operator attribute "negator" cannot be changed
+-- test cannot set non existant function
 ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = non_existent_func);
 ERROR:  function non_existent_func(internal, oid, internal, integer) does not exist
 ALTER OPERATOR === (boolean, boolean) SET (JOIN = non_existent_func);
 ERROR:  function non_existent_func(internal, oid, internal, smallint, internal) does not exist
-ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
-ERROR:  operator attribute "commutator" cannot be changed
-ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
-ERROR:  operator attribute "negator" cannot be changed
--- invalid: non-lowercase quoted identifiers
+-- test non-lowercase quoted identifiers invalid
 ALTER OPERATOR & (bit, bit) SET ("Restrict" = _int_contsel, "Join" = _int_contjoinsel);
 ERROR:  operator attribute "Restrict" not recognized
 --
--- Test permission check. Must be owner to ALTER OPERATOR.
+-- test must be owner of operator to ALTER OPERATOR.
 --
 CREATE USER regress_alter_op_user;
 SET SESSION AUTHORIZATION regress_alter_op_user;
 ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
 ERROR:  must be owner of operator ===
--- Clean up
 RESET SESSION AUTHORIZATION;
+--
+-- test set commutator, negator, hashes, and merges which can only be set if not
+-- already set
+--
+-- for these tests create operators with different left and right types so that
+-- we can validate that function signatures are handled correctly
+CREATE FUNCTION alter_op_test_fn_bool_real(boolean, real)
+RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
+CREATE FUNCTION alter_op_test_fn_real_bool(real, boolean)
+RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
+-- operator
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = real,
+    PROCEDURE = alter_op_test_fn_bool_real
+);
+-- commutator
+CREATE OPERATOR ==== (
+    LEFTARG = real,
+    RIGHTARG = boolean,
+    PROCEDURE = alter_op_test_fn_real_bool
+);
+-- negator
+CREATE OPERATOR !==== (
+    LEFTARG = boolean,
+    RIGHTARG = real,
+    PROCEDURE = alter_op_test_fn_bool_real
+);
+-- test no-op setting already false hashes and merges to false works
+ALTER OPERATOR === (boolean, real) SET (HASHES = false);
+ALTER OPERATOR === (boolean, real) SET (MERGES = false);
+-- validate still false after no-op
+SELECT oprcanhash, oprcanmerge FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+ oprcanhash | oprcanmerge 
+------------+-------------
+ f          | f
+(1 row)
+
+-- test cannot set commutator or negator without owning them
+SET SESSION AUTHORIZATION regress_alter_op_user;
+-- we need need a new operator owned by regress_alter_op_user so that we are
+-- allowed to alter it. ==== and !==== are owned by the test user so we expect
+-- the alters below to fail.
+CREATE OPERATOR ===@@@ (
+    LEFTARG = boolean,
+    RIGHTARG = real,
+    PROCEDURE = alter_op_test_fn_bool_real
+);
+ALTER OPERATOR ===@@@ (boolean, real) SET (COMMUTATOR= ====);
+ERROR:  must be owner of operator ====
+ALTER OPERATOR ===@@@ (boolean, real) SET (NEGATOR = !====);
+ERROR:  must be owner of operator !====
+-- validate operator is unchanged and commutator and negator are unset
+SELECT oprcom, oprnegate FROM pg_operator WHERE oprname = '===@@@'
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+ oprcom | oprnegate 
+--------+-----------
+      0 |         0
+(1 row)
+
+DROP OPERATOR ===@@@ (boolean, real);
+RESET SESSION AUTHORIZATION;
+-- test cannot set self negator
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = ===);
+ERROR:  operator cannot be its own negator
+-- validate no changes made
+SELECT oprcanmerge, oprcanhash, oprcom, oprnegate
+FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+ oprcanmerge | oprcanhash | oprcom | oprnegate 
+-------------+------------+--------+-----------
+ f           | f          |      0 |         0
+(1 row)
+
+-- test set hashes
+ALTER OPERATOR === (boolean, real) SET (HASHES);
+SELECT oprcanhash FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+ oprcanhash 
+------------
+ t
+(1 row)
+
+-- test set merges
+ALTER OPERATOR === (boolean, real) SET (MERGES);
+SELECT oprcanmerge FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+ oprcanmerge 
+-------------
+ t
+(1 row)
+
+-- test set commutator
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====);
+-- validate that the commutator has been set on both the operator and commutator,
+-- that they reference each other, and that the operator used is the existing
+-- one we created and not a new shell operator
+SELECT op.oprname AS operator_name, com.oprname AS commutator_name,
+  com.oprcode AS commutator_func
+  FROM pg_operator op
+  INNER JOIN pg_operator com ON (op.oid = com.oprcom AND op.oprcom = com.oid)
+  WHERE op.oprname = '==='
+  AND op.oprleft = 'boolean'::regtype AND op.oprright = 'real'::regtype;
+ operator_name | commutator_name |      commutator_func       
+---------------+-----------------+----------------------------
+ ===           | ====            | alter_op_test_fn_real_bool
+(1 row)
+
+-- test set negator
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====);
+-- validate that the negator has been set on both the operator and negator, that
+-- they reference each other, and that the operator used is the existing one we
+-- created and not a new shell operator
+SELECT op.oprname AS operator_name, neg.oprname AS negator_name,
+  neg.oprcode AS negator_func
+  FROM pg_operator op
+  INNER JOIN pg_operator neg ON (op.oid = neg.oprnegate AND op.oprnegate = neg.oid)
+  WHERE op.oprname = '==='
+  AND op.oprleft = 'boolean'::regtype AND op.oprright = 'real'::regtype;
+ operator_name | negator_name |        negator_func        
+---------------+--------------+----------------------------
+ ===           | !====        | alter_op_test_fn_bool_real
+(1 row)
+
+-- validate that the final state of the operator is as we expect
+SELECT oprcanmerge, oprcanhash,
+       pg_describe_object('pg_operator'::regclass, oprcom, 0) AS commutator,
+       pg_describe_object('pg_operator'::regclass, oprnegate, 0) AS negator
+  FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+ oprcanmerge | oprcanhash |         commutator          |           negator            
+-------------+------------+-----------------------------+------------------------------
+ t           | t          | operator ====(real,boolean) | operator !====(boolean,real)
+(1 row)
+
+-- test no-op set 'succeeds'
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====);
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====);
+ALTER OPERATOR === (boolean, real) SET (HASHES);
+ALTER OPERATOR === (boolean, real) SET (MERGES);
+-- test cannot change commutator, negator, hashes, and merges when already set
+CREATE OPERATOR @= (
+    LEFTARG = real,
+    RIGHTARG = boolean,
+    PROCEDURE = alter_op_test_fn_real_bool
+);
+CREATE OPERATOR @!= (
+    LEFTARG = boolean,
+    RIGHTARG = real,
+    PROCEDURE = alter_op_test_fn_bool_real
+);
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = @=);
+ERROR:  operator attribute "commutator" cannot be changed if it has already been set
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = @!=);
+ERROR:  operator attribute "negator" cannot be changed if it has already been set
+ALTER OPERATOR === (boolean, real) SET (HASHES = false);
+ERROR:  operator attribute "hashes" cannot be disabled after it has been enabled
+ALTER OPERATOR === (boolean, real) SET (MERGES = false);
+ERROR:  operator attribute "merges" cannot be disabled after it has been enabled
+-- test cannot set an operator that aleady has a commutator as the commutator
+ALTER OPERATOR @=(real, boolean) SET (COMMUTATOR = ===);
+ERROR:  commutator commutates another operator
+SELECT oprcom FROM pg_operator WHERE oprname = '@=';
+ oprcom 
+--------
+      0
+(1 row)
+
+-- test cannot set an operator that already has a negator as the negator
+ALTER OPERATOR @!=(boolean, real) SET (NEGATOR = ===);
+ERROR:  negator negates another operator
+SELECT oprnegate FROM pg_operator WHERE oprname = '@!=';
+ oprnegate 
+-----------
+         0
+(1 row)
+
+-- validate no changes made
+SELECT oprcanmerge, oprcanhash,
+       pg_describe_object('pg_operator'::regclass, oprcom, 0) AS commutator,
+       pg_describe_object('pg_operator'::regclass, oprnegate, 0) AS negator
+  FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+ oprcanmerge | oprcanhash |         commutator          |           negator            
+-------------+------------+-----------------------------+------------------------------
+ t           | t          | operator ====(real,boolean) | operator !====(boolean,real)
+(1 row)
+
+DROP OPERATOR @=(real, boolean);
+DROP OPERATOR @!=(boolean, real);
+--
+-- test setting commutator and negator to undefined or shell operator fails
+--
+DROP OPERATOR === (boolean, real);
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = real,
+    PROCEDURE = alter_op_test_fn_bool_real
+);
+-- test cannot set undefined
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ===@@@);
+ERROR:  operator ===@@@ does not exist
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = !===@@@);
+ERROR:  operator !===@@@ does not exist
+-- create shell operators for ===@@@ and !===@@@ 
+CREATE OPERATOR ===@@ (
+    LEFTARG = boolean,
+    RIGHTARG = real,
+    PROCEDURE = alter_op_test_fn_bool_real,
+    COMMUTATOR = ===@@@,
+    NEGATOR = !===@@@
+);
+-- test cannot set shell operators
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ===@@@);
+ERROR:  operator ===@@@ is undefined shell operator
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = !===@@@);
+ERROR:  operator !===@@@ is undefined shell operator
+-- validate no changes made
+SELECT oprcanmerge, oprcanhash,
+       pg_describe_object('pg_operator'::regclass, oprcom, 0) AS commutator,
+       pg_describe_object('pg_operator'::regclass, oprnegate, 0) AS negator
+  FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+ oprcanmerge | oprcanhash | commutator | negator 
+-------------+------------+------------+---------
+ f           | f          |            | 
+(1 row)
+
+DROP OPERATOR ===@@(boolean, real);
+DROP OPERATOR ===@@@(real, boolean);
+DROP OPERATOR !===@@@(boolean, real);
+--
+-- test setting self commutator
+--
+DROP OPERATOR === (boolean, boolean);
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = alter_op_test_fn
+);
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ===);
+-- validate that the oprcom is the operator oid
+SELECT oprname FROM pg_operator 
+  WHERE oprname = '===' AND oid = oprcom
+  AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+ oprname 
+---------
+ ===
+(1 row)
+
+--
+-- Clean up
+--
 DROP USER regress_alter_op_user;
 DROP OPERATOR === (boolean, boolean);
+DROP OPERATOR === (boolean, real);
+DROP OPERATOR ==== (real, boolean);
+DROP OPERATOR !==== (boolean, real);
 DROP FUNCTION customcontsel(internal, oid, internal, integer);
 DROP FUNCTION alter_op_test_fn(boolean, boolean);
+DROP FUNCTION alter_op_test_fn_bool_real(boolean, real);
+DROP FUNCTION alter_op_test_fn_real_bool(real, boolean);
diff --git a/src/test/regress/sql/alter_operator.sql b/src/test/regress/sql/alter_operator.sql
index fd40370165..85004d4191 100644
--- a/src/test/regress/sql/alter_operator.sql
+++ b/src/test/regress/sql/alter_operator.sql
@@ -22,7 +22,7 @@ WHERE classid = 'pg_operator'::regclass AND
 ORDER BY 1;
 
 --
--- Reset and set params
+-- test reset and set restrict and join
 --
 
 ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
@@ -71,30 +71,251 @@ WHERE classid = 'pg_operator'::regclass AND
       objid = '===(bool,bool)'::regoperator
 ORDER BY 1;
 
---
--- Test invalid options.
---
-ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
-ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+-- test cannot set non existant function
 ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = non_existent_func);
 ALTER OPERATOR === (boolean, boolean) SET (JOIN = non_existent_func);
-ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
-ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
 
--- invalid: non-lowercase quoted identifiers
+-- test non-lowercase quoted identifiers invalid
 ALTER OPERATOR & (bit, bit) SET ("Restrict" = _int_contsel, "Join" = _int_contjoinsel);
 
 --
--- Test permission check. Must be owner to ALTER OPERATOR.
+-- test must be owner of operator to ALTER OPERATOR.
 --
 CREATE USER regress_alter_op_user;
 SET SESSION AUTHORIZATION regress_alter_op_user;
 
 ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
 
--- Clean up
 RESET SESSION AUTHORIZATION;
+
+--
+-- test set commutator, negator, hashes, and merges which can only be set if not
+-- already set
+--
+
+-- for these tests create operators with different left and right types so that
+-- we can validate that function signatures are handled correctly
+
+CREATE FUNCTION alter_op_test_fn_bool_real(boolean, real)
+RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
+
+CREATE FUNCTION alter_op_test_fn_real_bool(real, boolean)
+RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
+
+-- operator
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = real,
+    PROCEDURE = alter_op_test_fn_bool_real
+);
+
+-- commutator
+CREATE OPERATOR ==== (
+    LEFTARG = real,
+    RIGHTARG = boolean,
+    PROCEDURE = alter_op_test_fn_real_bool
+);
+
+-- negator
+CREATE OPERATOR !==== (
+    LEFTARG = boolean,
+    RIGHTARG = real,
+    PROCEDURE = alter_op_test_fn_bool_real
+);
+
+-- test no-op setting already false hashes and merges to false works
+ALTER OPERATOR === (boolean, real) SET (HASHES = false);
+ALTER OPERATOR === (boolean, real) SET (MERGES = false);
+
+-- validate still false after no-op
+SELECT oprcanhash, oprcanmerge FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+
+-- test cannot set commutator or negator without owning them
+SET SESSION AUTHORIZATION regress_alter_op_user;
+
+-- we need need a new operator owned by regress_alter_op_user so that we are
+-- allowed to alter it. ==== and !==== are owned by the test user so we expect
+-- the alters below to fail.
+CREATE OPERATOR ===@@@ (
+    LEFTARG = boolean,
+    RIGHTARG = real,
+    PROCEDURE = alter_op_test_fn_bool_real
+);
+
+ALTER OPERATOR ===@@@ (boolean, real) SET (COMMUTATOR= ====);
+ALTER OPERATOR ===@@@ (boolean, real) SET (NEGATOR = !====);
+
+-- validate operator is unchanged and commutator and negator are unset
+SELECT oprcom, oprnegate FROM pg_operator WHERE oprname = '===@@@'
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+
+DROP OPERATOR ===@@@ (boolean, real);
+
+RESET SESSION AUTHORIZATION;
+
+-- test cannot set self negator
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = ===);
+
+-- validate no changes made
+SELECT oprcanmerge, oprcanhash, oprcom, oprnegate
+FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+
+-- test set hashes
+ALTER OPERATOR === (boolean, real) SET (HASHES);
+SELECT oprcanhash FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+
+-- test set merges
+ALTER OPERATOR === (boolean, real) SET (MERGES);
+SELECT oprcanmerge FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+
+-- test set commutator
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====);
+
+-- validate that the commutator has been set on both the operator and commutator,
+-- that they reference each other, and that the operator used is the existing
+-- one we created and not a new shell operator
+SELECT op.oprname AS operator_name, com.oprname AS commutator_name,
+  com.oprcode AS commutator_func
+  FROM pg_operator op
+  INNER JOIN pg_operator com ON (op.oid = com.oprcom AND op.oprcom = com.oid)
+  WHERE op.oprname = '==='
+  AND op.oprleft = 'boolean'::regtype AND op.oprright = 'real'::regtype;
+
+-- test set negator
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====);
+
+-- validate that the negator has been set on both the operator and negator, that
+-- they reference each other, and that the operator used is the existing one we
+-- created and not a new shell operator
+SELECT op.oprname AS operator_name, neg.oprname AS negator_name,
+  neg.oprcode AS negator_func
+  FROM pg_operator op
+  INNER JOIN pg_operator neg ON (op.oid = neg.oprnegate AND op.oprnegate = neg.oid)
+  WHERE op.oprname = '==='
+  AND op.oprleft = 'boolean'::regtype AND op.oprright = 'real'::regtype;
+
+-- validate that the final state of the operator is as we expect
+SELECT oprcanmerge, oprcanhash,
+       pg_describe_object('pg_operator'::regclass, oprcom, 0) AS commutator,
+       pg_describe_object('pg_operator'::regclass, oprnegate, 0) AS negator
+  FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+
+-- test no-op set 'succeeds'
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====);
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====);
+ALTER OPERATOR === (boolean, real) SET (HASHES);
+ALTER OPERATOR === (boolean, real) SET (MERGES);
+
+-- test cannot change commutator, negator, hashes, and merges when already set
+
+CREATE OPERATOR @= (
+    LEFTARG = real,
+    RIGHTARG = boolean,
+    PROCEDURE = alter_op_test_fn_real_bool
+);
+CREATE OPERATOR @!= (
+    LEFTARG = boolean,
+    RIGHTARG = real,
+    PROCEDURE = alter_op_test_fn_bool_real
+);
+
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = @=);
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = @!=);
+ALTER OPERATOR === (boolean, real) SET (HASHES = false);
+ALTER OPERATOR === (boolean, real) SET (MERGES = false);
+
+-- test cannot set an operator that aleady has a commutator as the commutator
+ALTER OPERATOR @=(real, boolean) SET (COMMUTATOR = ===);
+SELECT oprcom FROM pg_operator WHERE oprname = '@=';
+
+-- test cannot set an operator that already has a negator as the negator
+ALTER OPERATOR @!=(boolean, real) SET (NEGATOR = ===);
+SELECT oprnegate FROM pg_operator WHERE oprname = '@!=';
+
+-- validate no changes made
+SELECT oprcanmerge, oprcanhash,
+       pg_describe_object('pg_operator'::regclass, oprcom, 0) AS commutator,
+       pg_describe_object('pg_operator'::regclass, oprnegate, 0) AS negator
+  FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+
+DROP OPERATOR @=(real, boolean);
+DROP OPERATOR @!=(boolean, real);
+
+--
+-- test setting commutator and negator to undefined or shell operator fails
+--
+
+DROP OPERATOR === (boolean, real);
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = real,
+    PROCEDURE = alter_op_test_fn_bool_real
+);
+
+-- test cannot set undefined
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ===@@@);
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = !===@@@);
+
+-- create shell operators for ===@@@ and !===@@@ 
+CREATE OPERATOR ===@@ (
+    LEFTARG = boolean,
+    RIGHTARG = real,
+    PROCEDURE = alter_op_test_fn_bool_real,
+    COMMUTATOR = ===@@@,
+    NEGATOR = !===@@@
+);
+
+-- test cannot set shell operators
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ===@@@);
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = !===@@@);
+
+-- validate no changes made
+SELECT oprcanmerge, oprcanhash,
+       pg_describe_object('pg_operator'::regclass, oprcom, 0) AS commutator,
+       pg_describe_object('pg_operator'::regclass, oprnegate, 0) AS negator
+  FROM pg_operator WHERE oprname = '==='
+  AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+
+DROP OPERATOR ===@@(boolean, real);
+DROP OPERATOR ===@@@(real, boolean);
+DROP OPERATOR !===@@@(boolean, real);
+
+--
+-- test setting self commutator
+--
+
+DROP OPERATOR === (boolean, boolean);
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = alter_op_test_fn
+);
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ===);
+
+-- validate that the oprcom is the operator oid
+SELECT oprname FROM pg_operator 
+  WHERE oprname = '===' AND oid = oprcom
+  AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+
+--
+-- Clean up
+--
+
 DROP USER regress_alter_op_user;
+
 DROP OPERATOR === (boolean, boolean);
+DROP OPERATOR === (boolean, real);
+DROP OPERATOR ==== (real, boolean);
+DROP OPERATOR !==== (boolean, real);
+
 DROP FUNCTION customcontsel(internal, oid, internal, integer);
 DROP FUNCTION alter_op_test_fn(boolean, boolean);
+DROP FUNCTION alter_op_test_fn_bool_real(boolean, real);
+DROP FUNCTION alter_op_test_fn_real_bool(real, boolean);
