diff --git a/doc/src/sgml/ref/alter_operator.sgml b/doc/src/sgml/ref/alter_operator.sgml
index 8a7af50d604..b2eaa7a263e 100644
--- a/doc/src/sgml/ref/alter_operator.sgml
+++ b/doc/src/sgml/ref/alter_operator.sgml
@@ -26,6 +26,11 @@ ALTER OPERATOR name ( { left_typename ( { left_type | NONE } , { right_type | NONE } )
SET SCHEMA new_schema
+
+ALTER OPERATOR name ( { left_type | NONE } , { right_type | NONE } )
+ SET ( { RESTRICT = { res_proc | NONE }
+ | JOIN = { join_proc | NONE }
+ } [, ... ] )
@@ -34,8 +39,7 @@ ALTER OPERATOR name ( { left_type
ALTER OPERATOR changes the definition of
- an operator. The only currently available functionality is to change the
- owner of the operator.
+ an operator.
@@ -98,6 +102,25 @@ ALTER OPERATOR name ( { left_type
+
+
+ res_proc
+
+
+ The restriction selectivity estimator function for this operator; write NONE to remove existing selectivity estimator.
+
+
+
+
+
+ join_proc
+
+
+ The join selectivity estimator function for this operator; write NONE to remove existing selectivity estimator.
+
+
+
+
@@ -109,6 +132,13 @@ ALTER OPERATOR name ( { left_type
ALTER OPERATOR @@ (text, text) OWNER TO joe;
+
+
+ Change the restriction and join selectivity estimator functions of a custom operator a && b for type int[]:
+
+ALTER OPERATOR && (_int4, _int4) SET (RESTRICT = _int_contsel, JOIN = _int_contjoinsel);
+
+
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index b4a1aac3a14..32065185ea4 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -51,6 +51,9 @@
#include "utils/rel.h"
#include "utils/syscache.h"
+static Oid ValidateRestrictionEstimator(List *restrictionName);
+static Oid ValidateJoinEstimator(List *joinName);
+
/*
* DefineOperator
* this function extracts all the information from the
@@ -80,7 +83,7 @@ DefineOperator(List *names, List *parameters)
Oid functionOid; /* functions converted to OID */
Oid restrictionOid;
Oid joinOid;
- Oid typeId[5]; /* only need up to 5 args here */
+ Oid typeId[2]; /* to hold left and right arg */
int nargs;
ListCell *pl;
@@ -140,10 +143,13 @@ DefineOperator(List *names, List *parameters)
else if (pg_strcasecmp(defel->defname, "gtcmp") == 0)
canMerge = true;
else
+ {
+ /* WARNING, not ERROR, for historical backwards-compatibility */
ereport(WARNING,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("operator attribute \"%s\" not recognized",
defel->defname)));
+ }
}
/*
@@ -216,69 +222,14 @@ DefineOperator(List *names, List *parameters)
aclcheck_error_type(aclresult, rettype);
/*
- * Look up restriction estimator if specified
+ * Look up restriction and join estimators if specified
*/
if (restrictionName)
- {
- typeId[0] = INTERNALOID; /* PlannerInfo */
- typeId[1] = OIDOID; /* operator OID */
- typeId[2] = INTERNALOID; /* args list */
- typeId[3] = INT4OID; /* varRelid */
-
- restrictionOid = LookupFuncName(restrictionName, 4, typeId, false);
-
- /* estimators must return float8 */
- if (get_func_rettype(restrictionOid) != FLOAT8OID)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("restriction estimator function %s must return type \"float8\"",
- NameListToString(restrictionName))));
-
- /* Require EXECUTE rights for the estimator */
- aclresult = pg_proc_aclcheck(restrictionOid, GetUserId(), ACL_EXECUTE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_PROC,
- NameListToString(restrictionName));
- }
+ restrictionOid = ValidateRestrictionEstimator(restrictionName);
else
restrictionOid = InvalidOid;
-
- /*
- * Look up join estimator if specified
- */
if (joinName)
- {
- typeId[0] = INTERNALOID; /* PlannerInfo */
- typeId[1] = OIDOID; /* operator OID */
- typeId[2] = INTERNALOID; /* args list */
- typeId[3] = INT2OID; /* jointype */
- typeId[4] = INTERNALOID; /* SpecialJoinInfo */
-
- /*
- * As of Postgres 8.4, the preferred signature for join estimators has
- * 5 arguments, but we still allow the old 4-argument form. Try the
- * preferred form first.
- */
- joinOid = LookupFuncName(joinName, 5, typeId, true);
- if (!OidIsValid(joinOid))
- joinOid = LookupFuncName(joinName, 4, typeId, true);
- /* If not found, reference the 5-argument signature in error msg */
- if (!OidIsValid(joinOid))
- joinOid = LookupFuncName(joinName, 5, typeId, false);
-
- /* estimators must return float8 */
- if (get_func_rettype(joinOid) != FLOAT8OID)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("join estimator function %s must return type \"float8\"",
- NameListToString(joinName))));
-
- /* Require EXECUTE rights for the estimator */
- aclresult = pg_proc_aclcheck(joinOid, GetUserId(), ACL_EXECUTE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_PROC,
- NameListToString(joinName));
- }
+ joinOid = ValidateJoinEstimator(joinName);
else
joinOid = InvalidOid;
@@ -299,6 +250,87 @@ DefineOperator(List *names, List *parameters)
canHash); /* operator hashes */
}
+/*
+ * Look up a restriction estimator function ny name, and verify that it has
+ * the correct signature and we have the permissions to attach it to an
+ * operator.
+ */
+static Oid
+ValidateRestrictionEstimator(List *restrictionName)
+{
+ Oid typeId[4];
+ Oid restrictionOid;
+ AclResult aclresult;
+
+ typeId[0] = INTERNALOID; /* PlannerInfo */
+ typeId[1] = OIDOID; /* operator OID */
+ typeId[2] = INTERNALOID; /* args list */
+ typeId[3] = INT4OID; /* varRelid */
+
+ restrictionOid = LookupFuncName(restrictionName, 4, typeId, false);
+
+ /* estimators must return float8 */
+ if (get_func_rettype(restrictionOid) != FLOAT8OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("restriction estimator function %s must return type \"float8\"",
+ NameListToString(restrictionName))));
+
+ /* Require EXECUTE rights for the estimator */
+ aclresult = pg_proc_aclcheck(restrictionOid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC,
+ NameListToString(restrictionName));
+
+ return restrictionOid;
+}
+
+/*
+ * Look up a join estimator function ny name, and verify that it has the
+ * correct signature and we have the permissions to attach it to an
+ * operator.
+ */
+static Oid
+ValidateJoinEstimator(List *joinName)
+{
+ Oid typeId[5];
+ Oid joinOid;
+ AclResult aclresult;
+
+ typeId[0] = INTERNALOID; /* PlannerInfo */
+ typeId[1] = OIDOID; /* operator OID */
+ typeId[2] = INTERNALOID; /* args list */
+ typeId[3] = INT2OID; /* jointype */
+ typeId[4] = INTERNALOID; /* SpecialJoinInfo */
+
+ /*
+ * As of Postgres 8.4, the preferred signature for join estimators has 5
+ * arguments, but we still allow the old 4-argument form. Try the
+ * preferred form first.
+ */
+ joinOid = LookupFuncName(joinName, 5, typeId, true);
+ if (!OidIsValid(joinOid))
+ joinOid = LookupFuncName(joinName, 4, typeId, true);
+ /* If not found, reference the 5-argument signature in error msg */
+ if (!OidIsValid(joinOid))
+ joinOid = LookupFuncName(joinName, 5, typeId, false);
+
+ /* estimators must return float8 */
+ if (get_func_rettype(joinOid) != FLOAT8OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("join estimator function %s must return type \"float8\"",
+ NameListToString(joinName))));
+
+ /* Require EXECUTE rights for the estimator */
+ aclresult = pg_proc_aclcheck(joinOid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC,
+ NameListToString(joinName));
+
+ return joinOid;
+}
+
/*
* Guts of operator deletion.
*/
@@ -320,3 +352,154 @@ RemoveOperatorById(Oid operOid)
heap_close(relation, RowExclusiveLock);
}
+
+/*
+ * AlterOperator
+ * routine implementing ALTER OPERATOR SET (option = ...).
+ *
+ * Currently, only RESTRICT and JOIN estimator functions can be changed.
+ */
+ObjectAddress
+AlterOperator(AlterOperatorStmt *stmt)
+{
+ ObjectAddress address;
+ Oid oprId;
+ Relation catalog;
+ HeapTuple tup;
+ Form_pg_operator oprForm;
+ int i;
+ ListCell *pl;
+ Datum values[Natts_pg_operator];
+ bool nulls[Natts_pg_operator];
+ bool replaces[Natts_pg_operator];
+ List *restrictionName = NIL; /* optional restrict. sel. procedure */
+ bool updateRestriction = false;
+ Oid restrictionOid;
+ List *joinName = NIL; /* optional join sel. procedure */
+ bool updateJoin = false;
+ Oid joinOid;
+
+ /* Look up the operator */
+ oprId = LookupOperNameTypeNames(NULL, stmt->opername,
+ (TypeName *) linitial(stmt->operargs),
+ (TypeName *) lsecond(stmt->operargs),
+ false, -1);
+ catalog = heap_open(OperatorRelationId, RowExclusiveLock);
+ tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(oprId));
+ if (tup == NULL)
+ elog(ERROR, "cache lookup failed for operator %u", oprId);
+ oprForm = (Form_pg_operator) GETSTRUCT(tup);
+
+ /* Process options */
+ foreach(pl, stmt->options)
+ {
+ DefElem *defel = (DefElem *) lfirst(pl);
+ List *param;
+
+ if (defel->arg == NULL)
+ param = NIL; /* NONE, removes the function */
+ else
+ param = defGetQualifiedName(defel);
+
+ if (pg_strcasecmp(defel->defname, "restrict") == 0)
+ {
+ restrictionName = param;
+ updateRestriction = true;
+ }
+ else if (pg_strcasecmp(defel->defname, "join") == 0)
+ {
+ joinName = param;
+ updateJoin = true;
+ }
+
+ /*
+ * The rest of the options that CREATE accepts cannot be changed.
+ * Check for them so that we can give a meaningful error message.
+ */
+ else if (pg_strcasecmp(defel->defname, "leftarg") == 0 ||
+ pg_strcasecmp(defel->defname, "rightarg") == 0 ||
+ pg_strcasecmp(defel->defname, "procedure") == 0 ||
+ pg_strcasecmp(defel->defname, "commutator") == 0 ||
+ pg_strcasecmp(defel->defname, "negator") == 0 ||
+ pg_strcasecmp(defel->defname, "hashes") == 0 ||
+ pg_strcasecmp(defel->defname, "merges") == 0)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("operator attribute \"%s\" can not be changed",
+ defel->defname)));
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("operator attribute \"%s\" not recognized",
+ defel->defname)));
+ }
+
+ /* Check permissions. Must be owner. */
+ if (!pg_oper_ownercheck(oprId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER,
+ NameStr(oprForm->oprname));
+
+ /*
+ * Look up restriction and join estimators if specified
+ */
+ if (restrictionName)
+ restrictionOid = ValidateRestrictionEstimator(restrictionName);
+ else
+ restrictionOid = InvalidOid;
+ if (joinName)
+ joinOid = ValidateJoinEstimator(joinName);
+ else
+ joinOid = InvalidOid;
+
+ /* Perform additional checks, like OperatorCreate does */
+ if (!(OidIsValid(oprForm->oprleft) && OidIsValid(oprForm->oprright)))
+ {
+ /* 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")));
+ }
+
+ if (oprForm->oprresult != BOOLOID)
+ {
+ if (OidIsValid(restrictionOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("only boolean operators can have restriction selectivity")));
+ if (OidIsValid(joinOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("only boolean operators can have join selectivity")));
+ }
+
+ /* Update the tuple */
+ for (i = 0; i < Natts_pg_operator; ++i)
+ {
+ values[i] = (Datum) 0;
+ replaces[i] = false;
+ nulls[i] = false;
+ }
+ if (updateRestriction)
+ {
+ replaces[Anum_pg_operator_oprrest - 1] = true;
+ values[Anum_pg_operator_oprrest - 1] = restrictionOid;
+ }
+ if (updateJoin)
+ {
+ replaces[Anum_pg_operator_oprjoin - 1] = true;
+ values[Anum_pg_operator_oprjoin - 1] = joinOid;
+ }
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(catalog),
+ values, nulls, replaces);
+
+ simple_heap_update(catalog, &tup->t_self, tup);
+ CatalogUpdateIndexes(catalog, tup);
+
+ heap_close(catalog, RowExclusiveLock);
+
+ return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4c363d3d39a..6a08c2db211 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3212,6 +3212,18 @@ _copyAlterOwnerStmt(const AlterOwnerStmt *from)
return newnode;
}
+static AlterOperatorStmt *
+_copyAlterOperatorStmt(const AlterOperatorStmt *from)
+{
+ AlterOperatorStmt *newnode = makeNode(AlterOperatorStmt);
+
+ COPY_NODE_FIELD(opername);
+ COPY_NODE_FIELD(operargs);
+ COPY_NODE_FIELD(options);
+
+ return newnode;
+}
+
static RuleStmt *
_copyRuleStmt(const RuleStmt *from)
{
@@ -4615,6 +4627,9 @@ copyObject(const void *from)
case T_AlterOwnerStmt:
retval = _copyAlterOwnerStmt(from);
break;
+ case T_AlterOperatorStmt:
+ retval = _copyAlterOperatorStmt(from);
+ break;
case T_RuleStmt:
retval = _copyRuleStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f19251e7c41..faf5eedab4e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1338,6 +1338,16 @@ _equalAlterOwnerStmt(const AlterOwnerStmt *a, const AlterOwnerStmt *b)
return true;
}
+static bool
+_equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b)
+{
+ COMPARE_NODE_FIELD(opername);
+ COMPARE_NODE_FIELD(operargs);
+ COMPARE_NODE_FIELD(options);
+
+ return true;
+}
+
static bool
_equalRuleStmt(const RuleStmt *a, const RuleStmt *b)
{
@@ -2980,6 +2990,9 @@ equal(const void *a, const void *b)
case T_AlterOwnerStmt:
retval = _equalAlterOwnerStmt(a, b);
break;
+ case T_AlterOperatorStmt:
+ retval = _equalAlterOperatorStmt(a, b);
+ break;
case T_RuleStmt:
retval = _equalRuleStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e0ff6f16a21..2b02a2e5233 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -232,7 +232,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
AlterEventTrigStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
- AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
+ AlterObjectSchemaStmt AlterOwnerStmt AlterOperatorStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt
@@ -359,7 +359,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
any_operator expr_list attrs
target_list opt_target_list insert_column_list set_target_list
set_clause_list set_clause multiple_set_clause
- ctext_expr_list ctext_row def_list indirection opt_indirection
+ ctext_expr_list ctext_row def_list operator_def_list indirection opt_indirection
reloption_list group_clause TriggerFuncArgs select_limit
opt_select_limit opclass_item_list opclass_drop_list
opclass_purpose opt_opfamily transaction_mode_list_or_empty
@@ -432,7 +432,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type TableElement TypedTableElement ConstraintElem TableFuncElement
%type columnDef columnOptions
-%type def_elem reloption_elem old_aggr_elem
+%type def_elem reloption_elem old_aggr_elem operator_def_elem
%type def_arg columnElem where_clause where_or_current_clause
a_expr b_expr c_expr AexprConst indirection_el
columnref in_expr having_clause func_table array_expr
@@ -769,6 +769,7 @@ stmt :
| AlterGroupStmt
| AlterObjectSchemaStmt
| AlterOwnerStmt
+ | AlterOperatorStmt
| AlterPolicyStmt
| AlterSeqStmt
| AlterSystemStmt
@@ -8196,6 +8197,33 @@ AlterObjectSchemaStmt:
}
;
+/*****************************************************************************
+ *
+ * ALTER OPERATOR name SET define
+ *
+ *****************************************************************************/
+
+AlterOperatorStmt:
+ ALTER OPERATOR any_operator oper_argtypes SET '(' operator_def_list ')'
+ {
+ AlterOperatorStmt *n = makeNode(AlterOperatorStmt);
+ n->opername = $3;
+ n->operargs = $4;
+ n->options = $7;
+ $$ = (Node *)n;
+ }
+ ;
+
+operator_def_list: operator_def_elem { $$ = list_make1($1); }
+ | operator_def_list ',' operator_def_elem { $$ = lappend($1, $3); }
+ ;
+
+operator_def_elem: ColLabel '=' NONE
+ { $$ = makeDefElem($1, NULL); }
+ | ColLabel '=' def_arg
+ { $$ = makeDefElem($1, (Node *) $3); }
+ ;
+
/*****************************************************************************
*
* ALTER THING name OWNER TO newname
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 0dabcc130e0..e81bbc6c9d1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -149,6 +149,7 @@ check_xact_readonly(Node *parsetree)
case T_AlterRoleSetStmt:
case T_AlterObjectSchemaStmt:
case T_AlterOwnerStmt:
+ case T_AlterOperatorStmt:
case T_AlterSeqStmt:
case T_AlterTableMoveAllStmt:
case T_AlterTableStmt:
@@ -1481,6 +1482,10 @@ ProcessUtilitySlow(Node *parsetree,
address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
break;
+ case T_AlterOperatorStmt:
+ address = AlterOperator((AlterOperatorStmt *) parsetree);
+ break;
+
case T_CommentStmt:
address = CommentObject((CommentStmt *) parsetree);
break;
@@ -2494,6 +2499,10 @@ CreateCommandTag(Node *parsetree)
tag = "ALTER OPERATOR FAMILY";
break;
+ case T_AlterOperatorStmt:
+ tag = "ALTER OPERATOR";
+ break;
+
case T_AlterTSDictionaryStmt:
tag = "ALTER TEXT SEARCH DICTIONARY";
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 9b81c16d823..adae296f527 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -73,6 +73,7 @@ extern void interpret_function_parameter_list(List *parameters,
/* commands/operatorcmds.c */
extern ObjectAddress DefineOperator(List *names, List *parameters);
extern void RemoveOperatorById(Oid operOid);
+extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/aggregatecmds.c */
extern ObjectAddress DefineAggregate(List *name, List *args, bool oldstyle,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 290cdb30585..f8acda4eede 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -347,6 +347,7 @@ typedef enum NodeTag
T_DropTableSpaceStmt,
T_AlterObjectSchemaStmt,
T_AlterOwnerStmt,
+ T_AlterOperatorStmt,
T_DropOwnedStmt,
T_ReassignOwnedStmt,
T_CompositeTypeStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a567c50da72..5aaf5ec9e8e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2543,6 +2543,19 @@ typedef struct AlterOwnerStmt
} AlterOwnerStmt;
+/* ----------------------
+ * Alter Operator Set Restrict, Join
+ * ----------------------
+ */
+typedef struct AlterOperatorStmt
+{
+ NodeTag type;
+ List *opername; /* operator name */
+ List *operargs; /* operator's argument TypeNames */
+ List *options; /* List of DefElem nodes */
+} AlterOperatorStmt;
+
+
/* ----------------------
* Create Rule Statement
* ----------------------
diff --git a/src/test/regress/expected/alter_operator.out b/src/test/regress/expected/alter_operator.out
new file mode 100644
index 00000000000..2b996544c1c
--- /dev/null
+++ b/src/test/regress/expected/alter_operator.out
@@ -0,0 +1,76 @@
+CREATE OR REPLACE FUNCTION alter_op_test_fn(boolean, boolean)
+RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+ LEFTARG = boolean,
+ RIGHTARG = boolean,
+ PROCEDURE = alter_op_test_fn,
+ COMMUTATOR = ===,
+ NEGATOR = !==,
+ RESTRICT = contsel,
+ JOIN = contjoinsel,
+ HASHES, MERGES
+);
+--
+-- Reset and set params
+--
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NONE);
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+ oprrest | oprjoin
+---------+---------
+ - | -
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = contjoinsel);
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+ oprrest | oprjoin
+---------+-------------
+ contsel | contjoinsel
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE, JOIN = NONE);
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+ oprrest | oprjoin
+---------+---------
+ - | -
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel, JOIN = contjoinsel);
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+ oprrest | oprjoin
+---------+-------------
+ contsel | contjoinsel
+(1 row)
+
+--
+-- Test invalid options.
+--
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+ERROR: operator attribute "commutator" can not be changed
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+ERROR: operator attribute "negator" can not be changed
+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" can not be changed
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+ERROR: operator attribute "negator" can not be changed
+--
+-- Test permission check. Must be owner to ALTER OPERATOR.
+--
+CREATE USER regtest_alter_user;
+SET SESSION AUTHORIZATION regtest_alter_user_user;
+ERROR: role "regtest_alter_user_user" does not exist
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
+RESET SESSION AUTHORIZATION;
+-- Clean up
+DROP USER regression_alter_user;
+ERROR: role "regression_alter_user" does not exist
+DROP OPERATOR === (boolean, boolean);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 91780cdcc73..4df15def6e2 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -89,7 +89,7 @@ test: brin gin gist spgist privileges security_label collate matview lock replic
# ----------
# Another group of parallel tests
# ----------
-test: alter_generic misc psql async
+test: alter_generic alter_operator misc psql async
# rules cannot run concurrently with any test that creates a view
test: rules
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index a2e0cebbdb5..3a607cff46c 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -111,6 +111,7 @@ test: replica_identity
test: rowsecurity
test: object_address
test: alter_generic
+test: alter_operator
test: misc
test: psql
test: async
diff --git a/src/test/regress/sql/alter_operator.sql b/src/test/regress/sql/alter_operator.sql
new file mode 100644
index 00000000000..535052731a4
--- /dev/null
+++ b/src/test/regress/sql/alter_operator.sql
@@ -0,0 +1,64 @@
+CREATE OR REPLACE FUNCTION alter_op_test_fn(boolean, boolean)
+RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
+
+CREATE OPERATOR === (
+ LEFTARG = boolean,
+ RIGHTARG = boolean,
+ PROCEDURE = alter_op_test_fn,
+ COMMUTATOR = ===,
+ NEGATOR = !==,
+ RESTRICT = contsel,
+ JOIN = contjoinsel,
+ HASHES, MERGES
+);
+
+--
+-- Reset and set params
+--
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NONE);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE, JOIN = NONE);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel, JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+
+--
+-- Test invalid options.
+--
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+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 = !==);
+
+
+--
+-- Test permission check. Must be owner to ALTER OPERATOR.
+--
+CREATE USER regtest_alter_user;
+SET SESSION AUTHORIZATION regtest_alter_user_user;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
+
+RESET SESSION AUTHORIZATION;
+
+-- Clean up
+DROP USER regression_alter_user;
+DROP OPERATOR === (boolean, boolean);