WIP: Enhanced ALTER OPERATOR

Started by Uriy Zhuravlevover 10 years ago25 messages
#1Uriy Zhuravlev
u.zhuravlev@postgrespro.ru
1 attachment(s)

Hello Hackers.

I have attached a patch that extends ALTER OPERATOR to support COMMUTATOR,
NEGATOR, RESTRICT and JOIN. This patch is based on master. It is small patch
with regression tests.

Why do it?

The operator has four important parameters that can be set only during the
creation. These are: commutator (oprcom), negator (oprnegate), restrict
(oprrest), join (oprjoin). For example, you created operator with RESTRICT =
contsel . After a while you began to actively use your new operator. Then you
develop a new function for RESTRICT (my_restrict_func). To change the
RESTRICT operator you have to create a new database and to migrate there
because operator is used and you can't DROP OPERATOR and CREATE OPERATOR
again. The fact that it is extremely difficult sometimes almost think it is
clear to all.
It is interesting that the change in the parameters of the operator takes
place periodically for the built-in operators (when changing major version),
but it is impossible for users defined operators.
Real life example is intarray ( /messages/by-id/CAPpHfdssY+qEPDCOvxx-b4LP3ybR+qS04M6-ARgGKNFk3FrFow@mail.gmail.com ).
Also using ALTER OPERATOR for self-linkage more logical than make operator
shells.

Simple syntax example:
ALTER OPERATOR === (boolean, boolean) SET COMMUTATOR =;
ALTER OPERATOR === (boolean, boolean) SET NEGATOR =;
ALTER OPERATOR === (boolean, boolean) SET RESTRICT example_func;
ALTER OPERATOR === (boolean, boolean) SET JOIN example_func;

ALTER OPERATOR === (boolean, boolean) SET COMMUTATOR NONE;
ALTER OPERATOR === (boolean, boolean) SET NEGATOR NONE;
ALTER OPERATOR === (boolean, boolean) SET RESTRICT NONE;
ALTER OPERATOR === (boolean, boolean) SET JOIN NONE;

It seems to me a syntax similar to the classic ALTER will be better than what
was used in the CREATE OPERATOR.

In this patch I am:
1. renamed OperatorUpd to ShellOperatorUpd. It more right name.
2. created AlterOperatorStmt struct for parsing command.
3. created ExecAlterOperatorStmt function for check user rights and select
parameter for edit.
4. recreated OperatorUpd for update params of operator in catalog.
5. And other small fix for extend parser.

In AlterOperatorStmt confuses me to use const char for cmd_name. In addition,
I clean only the catalog cache but judging by how works shell operators,
nothing more is needed.

Thanks!

--
Uriy Zhuravlev
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

extend_alter_operator_v1.patchtext/x-patch; charset=UTF-8; name=extend_alter_operator_v1.patchDownload
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 072f530..7ff95c1 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_func.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -53,7 +54,7 @@ static Oid OperatorShellMake(const char *operatorName,
 				  Oid leftTypeId,
 				  Oid rightTypeId);
 
-static void OperatorUpd(Oid baseId, Oid commId, Oid negId);
+static void ShellOperatorUpd(Oid baseId, Oid commId, Oid negId);
 
 static Oid get_other_operator(List *otherOp,
 				   Oid otherLeftTypeId, Oid otherRightTypeId,
@@ -563,7 +564,7 @@ OperatorCreate(const char *operatorName,
 		commutatorId = operatorObjectId;
 
 	if (OidIsValid(commutatorId) || OidIsValid(negatorId))
-		OperatorUpd(operatorObjectId, commutatorId, negatorId);
+		ShellOperatorUpd(operatorObjectId, commutatorId, negatorId);
 
 	return address;
 }
@@ -633,7 +634,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
 }
 
 /*
- * OperatorUpd
+ * ShellOperatorUpd
  *
  *	For a given operator, look up its negator and commutator operators.
  *	If they are defined, but their negator and commutator fields
@@ -642,7 +643,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
  *	which are the negator or commutator of each other.
  */
 static void
-OperatorUpd(Oid baseId, Oid commId, Oid negId)
+ShellOperatorUpd(Oid baseId, Oid commId, Oid negId)
 {
 	int			i;
 	Relation	pg_operator_desc;
@@ -864,3 +865,164 @@ makeOperatorDependencies(HeapTuple tuple)
 
 	return myself;
 }
+
+/*
+ * Operator update aka ALTER OPERATOR for COMMUTATOR, NEGATOR, RESTRICT, JOIN
+ */
+void OperatorUpd(Oid classId,
+				 Oid baseId,
+				 List *operator_param,
+				 unsigned int operator_param_type)
+{
+	int			i;
+	Relation	catalog;
+	HeapTuple	tup;
+	Oid 		operator_param_id = 0;
+	Form_pg_operator DstOperatorData;
+	bool		otherDefined;
+	bool		nulls[Natts_pg_operator];
+	bool		replaces[Natts_pg_operator];
+	Datum		values[Natts_pg_operator];
+
+	for (i = 0; i < Natts_pg_operator; ++i)
+	{
+		values[i] = (Datum) 0;
+		replaces[i] = false;
+		nulls[i] = false;
+	}
+
+	catalog = heap_open(classId, RowExclusiveLock);
+	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(baseId));
+	if (HeapTupleIsValid(tup))
+	{
+		DstOperatorData = (Form_pg_operator) GETSTRUCT(tup);
+
+		/*
+		 * Prepare tuple to upgrade the operator
+		 * considering the type of the parameter.
+		 */
+		if (operator_param_type == Anum_pg_operator_oprcom ||
+			operator_param_type == Anum_pg_operator_oprnegate)
+		{
+			otherDefined = true;
+			if (PointerIsValid(operator_param))
+				operator_param_id = OperatorLookup(operator_param,
+											DstOperatorData->oprleft,
+											DstOperatorData->oprright,
+											&otherDefined);
+			else
+				operator_param_id = InvalidOid;
+
+			if (!otherDefined && OidIsValid(operator_param_id)) {
+				ereport(ERROR,
+					(errmsg_internal("You can't set shell (fake) operator")));
+			}
+		}
+		else if (operator_param_type == Anum_pg_operator_oprrest)
+		{
+			/* Resets if written NONE */
+			if (pg_strcasecmp(NameListToString(operator_param), "none") == 0)
+				operator_param = NULL;
+
+			if (PointerIsValid(operator_param))
+			{
+				Oid			typeId[5];
+				AclResult	aclresult;
+				typeId[0] = INTERNALOID;	/* PlannerInfo */
+				typeId[1] = OIDOID;		/* operator OID */
+				typeId[2] = INTERNALOID;	/* args list */
+				typeId[3] = INT4OID;	/* varRelid */
+
+				operator_param_id = LookupFuncName(operator_param, 4, typeId, false);
+
+				/* estimators must return float8 */
+				if (get_func_rettype(operator_param_id) != FLOAT8OID)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("restriction estimator function %s must return type \"float8\"",
+									NameListToString(operator_param))));
+
+				/* Require EXECUTE rights for the estimator */
+				aclresult = pg_proc_aclcheck(operator_param_id, GetUserId(), ACL_EXECUTE);
+				if (aclresult != ACLCHECK_OK)
+					aclcheck_error(aclresult, ACL_KIND_PROC,
+								   NameListToString(operator_param));
+			}
+			else
+				operator_param_id = 0;
+		}
+		else if (operator_param_type == Anum_pg_operator_oprjoin)
+		{
+			/* Resets if written NONE */
+			if (pg_strcasecmp(NameListToString(operator_param), "none") == 0)
+				operator_param = NULL;
+
+			if (PointerIsValid(operator_param))
+			{
+				Oid			typeId[5];
+				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.
+				 */
+				operator_param_id = LookupFuncName(operator_param, 5, typeId, true);
+				if (!OidIsValid(operator_param_id))
+					operator_param_id = LookupFuncName(operator_param, 4, typeId, true);
+				/* If not found, reference the 5-argument signature in error msg */
+				if (!OidIsValid(operator_param_id))
+					operator_param_id = LookupFuncName(operator_param, 5, typeId, false);
+
+				/* estimators must return float8 */
+				if (get_func_rettype(operator_param_id) != FLOAT8OID)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("restriction estimator function %s must return type \"float8\"",
+									NameListToString(operator_param))));
+
+				/* Require EXECUTE rights for the estimator */
+				aclresult = pg_proc_aclcheck(operator_param_id, GetUserId(), ACL_EXECUTE);
+				if (aclresult != ACLCHECK_OK)
+					aclcheck_error(aclresult, ACL_KIND_PROC,
+								   NameListToString(operator_param));
+			}
+			else
+				operator_param_id = 0;
+		}
+
+		/* Update heap */
+		if (OidIsValid(operator_param_id) || !PointerIsValid(operator_param))
+		{
+			tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(baseId));
+			if (HeapTupleIsValid(tup))
+			{
+				values[operator_param_type - 1] = ObjectIdGetDatum(operator_param_id);
+				replaces[operator_param_type - 1] = true;
+
+				tup = heap_modify_tuple(tup,
+										RelationGetDescr(catalog),
+										values,
+										nulls,
+										replaces);
+
+				simple_heap_update(catalog, &tup->t_self, tup);
+
+				CatalogUpdateIndexes(catalog, tup);
+
+				values[operator_param_type - 1] = (Datum) NULL;
+				replaces[operator_param_type - 1] = false;
+			}
+		}
+		else
+			ereport(ERROR,
+					(errmsg_internal("Not found function or operator for alter operator")));
+	}
+
+	heap_close(catalog, RowExclusiveLock);
+}
\ No newline at end of file
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index b4a1aac..73ca2c6 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -320,3 +320,100 @@ RemoveOperatorById(Oid operOid)
 
 	heap_close(relation, RowExclusiveLock);
 }
+
+ObjectAddress
+ExecAlterOperatorStmt(AlterOperatorStmt *stmt)
+{
+	ObjectAddress address;
+	Relation	catalog;
+	Relation	relation;
+	Datum		datum;
+	Oid			ownerId;
+	bool		isnull;
+	HeapTuple	tup;
+
+	/* Address to be modified operator. */
+	address = get_object_address(OBJECT_OPERATOR,
+								 stmt->object,
+								 stmt->objarg,
+								 &relation,
+								 AccessExclusiveLock,
+								 false);
+	Assert(relation == NULL);
+
+	/* Check user rights. */
+	if (!superuser())
+	{
+		AclObjectKind 	aclkind = get_object_aclkind(address.classId);
+		AttrNumber		Anum_name = get_object_attnum_name(address.classId);
+		AttrNumber		Anum_owner = get_object_attnum_owner(address.classId);;
+
+		catalog = heap_open(address.classId, RowExclusiveLock);
+
+		tup = get_catalog_object_by_oid(catalog, address.objectId);
+
+		if (tup == NULL)
+			elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+				 address.objectId, RelationGetRelationName(catalog));
+
+		datum = heap_getattr(tup, Anum_owner,
+							 RelationGetDescr(catalog), &isnull);
+		Assert(!isnull);
+		ownerId = DatumGetObjectId(datum);
+
+		/* must be owner */
+		if (!has_privs_of_role(GetUserId(), ownerId))
+		{
+			char	   *objname;
+			char		namebuf[NAMEDATALEN];
+
+			if (Anum_name != InvalidAttrNumber)
+			{
+				datum = heap_getattr(tup, Anum_name,
+									 RelationGetDescr(catalog), &isnull);
+				Assert(!isnull);
+				objname = NameStr(*DatumGetName(datum));
+			}
+			else
+			{
+				snprintf(namebuf, sizeof(namebuf), "%u",
+						 HeapTupleGetOid(tup));
+				objname = namebuf;
+			}
+			aclcheck_error(ACLCHECK_NOT_OWNER, aclkind, objname);
+		}
+		heap_close(catalog, RowExclusiveLock);
+	}
+
+	/* Select parameter for change */
+	if (pg_strcasecmp(stmt->cmd_name, "commutator")  == 0)
+	{
+		OperatorUpd(address.classId,
+				address.objectId,
+				stmt->oprparam,
+				Anum_pg_operator_oprcom);
+	}
+	else if (pg_strcasecmp(stmt->cmd_name, "negator")  == 0)
+	{
+		OperatorUpd(address.classId,
+				address.objectId,
+				stmt->oprparam,
+				Anum_pg_operator_oprnegate);
+	}
+	else if (pg_strcasecmp(stmt->cmd_name, "restrict")  == 0)
+	{
+		OperatorUpd(address.classId,
+				address.objectId,
+				stmt->oprparam,
+				Anum_pg_operator_oprrest);
+	}
+	else if (pg_strcasecmp(stmt->cmd_name, "join")  == 0)
+	{
+		OperatorUpd(address.classId,
+				address.objectId,
+				stmt->oprparam,
+				Anum_pg_operator_oprjoin);
+	}
+
+	return address;
+}
\ No newline at end of file
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 76b63af..aa6796d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3130,6 +3130,21 @@ _copyAlterOwnerStmt(const AlterOwnerStmt *from)
 	return newnode;
 }
 
+static AlterOperatorStmt *
+_copyAlterOperatorStmt(const AlterOperatorStmt *from)
+{
+	AlterOperatorStmt *newnode = makeNode(AlterOperatorStmt);
+
+	COPY_NODE_FIELD(relation);
+	COPY_NODE_FIELD(object);
+	COPY_NODE_FIELD(objarg);
+	COPY_NODE_FIELD(oprparam);
+	COPY_STRING_FIELD(cmd_name);
+	COPY_SCALAR_FIELD(missing_ok);
+
+	return newnode;
+}
+
 static RuleStmt *
 _copyRuleStmt(const RuleStmt *from)
 {
@@ -4526,6 +4541,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 e032142..06e4ba0 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1324,6 +1324,19 @@ _equalAlterOwnerStmt(const AlterOwnerStmt *a, const AlterOwnerStmt *b)
 }
 
 static bool
+_equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b)
+{
+	COMPARE_NODE_FIELD(relation);
+	COMPARE_NODE_FIELD(object);
+	COMPARE_NODE_FIELD(objarg);
+	COMPARE_NODE_FIELD(oprparam);
+	COMPARE_STRING_FIELD(cmd_name);
+	COMPARE_SCALAR_FIELD(missing_ok);
+
+	return true;
+}
+
+static bool
 _equalRuleStmt(const RuleStmt *a, const RuleStmt *b)
 {
 	COMPARE_NODE_FIELD(relation);
@@ -2920,6 +2933,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 e71d926..ec6c08d 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
@@ -561,7 +561,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
-	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
+	COMMITTED COMMUTATOR CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
 	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
 	CROSS CSV CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
@@ -597,7 +597,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
 
-	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE
+	NAME_P NAMES NATIONAL NATURAL NCHAR NEGATOR NEXT NO NONE
 	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
 	NULLS_P NUMERIC
 
@@ -755,6 +755,7 @@ stmt :
 			| AlterGroupStmt
 			| AlterObjectSchemaStmt
 			| AlterOwnerStmt
+			| AlterOperatorStmt
 			| AlterPolicyStmt
 			| AlterSeqStmt
 			| AlterSystemStmt
@@ -8173,6 +8174,76 @@ AlterObjectSchemaStmt:
 
 /*****************************************************************************
  *
+ * ALTER OPERATOR name SET THINGS name
+ *
+ *****************************************************************************/
+
+AlterOperatorStmt:
+			ALTER OPERATOR any_operator oper_argtypes SET COMMUTATOR any_operator
+				{
+					AlterOperatorStmt *n = makeNode(AlterOperatorStmt);
+					n->object = $3;
+					n->objarg = $4;
+					n->oprparam = $7;
+					n->cmd_name = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER OPERATOR any_operator oper_argtypes SET COMMUTATOR NONE
+				{
+					AlterOperatorStmt *n = makeNode(AlterOperatorStmt);
+					n->object = $3;
+					n->objarg = $4;
+					n->oprparam = NULL;
+					n->cmd_name = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER OPERATOR any_operator oper_argtypes SET NEGATOR any_operator
+				{
+					AlterOperatorStmt *n = makeNode(AlterOperatorStmt);
+					n->object = $3;
+					n->objarg = $4;
+					n->oprparam = $7;
+					n->cmd_name = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER OPERATOR any_operator oper_argtypes SET NEGATOR NONE
+				{
+					AlterOperatorStmt *n = makeNode(AlterOperatorStmt);
+					n->object = $3;
+					n->objarg = $4;
+					n->oprparam = NULL;
+					n->cmd_name = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER OPERATOR any_operator oper_argtypes SET RESTRICT handler_name
+				{
+					AlterOperatorStmt *n = makeNode(AlterOperatorStmt);
+					n->object = $3;
+					n->objarg = $4;
+					n->oprparam = $7;
+					n->cmd_name = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER OPERATOR any_operator oper_argtypes SET JOIN handler_name
+				{
+					AlterOperatorStmt *n = makeNode(AlterOperatorStmt);
+					n->object = $3;
+					n->objarg = $4;
+					n->oprparam = $7;
+					n->cmd_name = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+		;
+
+
+/*****************************************************************************
+ *
  * ALTER THING name OWNER TO newname
  *
  *****************************************************************************/
@@ -13464,6 +13535,7 @@ unreserved_keyword:
 			| COMMENTS
 			| COMMIT
 			| COMMITTED
+			| COMMUTATOR
 			| CONFIGURATION
 			| CONFLICT
 			| CONNECTION
@@ -13567,6 +13639,7 @@ unreserved_keyword:
 			| MOVE
 			| NAME_P
 			| NAMES
+			| NEGATOR
 			| NEXT
 			| NO
 			| NOTHING
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 78bfd34..d8b6bd7 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:
@@ -861,6 +862,19 @@ standard_ProcessUtility(Node *parsetree,
 			}
 			break;
 
+		case T_AlterOperatorStmt:
+			{
+				AlterOperatorStmt *stmt = (AlterOperatorStmt *) parsetree;
+				if (EventTriggerSupportsObjectType(OBJECT_OPERATOR)) {
+					ProcessUtilitySlow(parsetree, queryString,
+									   context, params,
+									   dest, completionTag);
+				}
+				else
+					ExecAlterOperatorStmt(stmt);
+			}
+			break;
+
 		case T_CommentStmt:
 			{
 				CommentStmt *stmt = (CommentStmt *) parsetree;
@@ -916,12 +930,14 @@ ProcessUtilitySlow(Node *parsetree,
 	ObjectAddress address;
 	ObjectAddress secondaryObject = InvalidObjectAddress;
 
+
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
 
 	/* PG_TRY block is to ensure we call EventTriggerEndCompleteQuery */
 	PG_TRY();
 	{
+
 		if (isCompleteQuery)
 			EventTriggerDDLCommandStart(parsetree);
 
@@ -1478,6 +1494,10 @@ ProcessUtilitySlow(Node *parsetree,
 				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_AlterOperatorStmt:
+				address = ExecAlterOperatorStmt((AlterOperatorStmt *) parsetree);
+				break;
+
 			case T_CommentStmt:
 				address = CommentObject((CommentStmt *) parsetree);
 				break;
@@ -2491,6 +2511,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/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index e22eb27..4af96fa 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1825,4 +1825,9 @@ extern ObjectAddress OperatorCreate(const char *operatorName,
 			   bool canMerge,
 			   bool canHash);
 
+extern void OperatorUpd(Oid classId,
+		Oid baseId,
+		List *operator_param,
+		unsigned int operator_param_type);
+
 #endif   /* PG_OPERATOR_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index c3a1748..c7df5ff 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -72,6 +72,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 ExecAlterOperatorStmt(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 768f413..d9f0eb4 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -343,6 +343,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 556c1c5..352c89c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2437,6 +2437,22 @@ typedef struct AlterOwnerStmt
 
 
 /* ----------------------
+ *		Alter Operator Set Commutator, Negator, Restrict, Join
+ * ----------------------
+ */
+typedef struct AlterOperatorStmt
+{
+	NodeTag		type;
+	RangeVar   *relation;		/* in case it's a table */
+	List	   *object;			/* in case it's some other object */
+	List	   *objarg;			/* argument types, if applicable */
+	List	   *oprparam;		/* operator */
+	const char	   *cmd_name;	/* COMMUTATOR, NEGATOR, RESTRICT, JOIN */
+	bool		missing_ok;		/* skip error if missing? */
+} AlterOperatorStmt;
+
+
+/* ----------------------
  *		Create Rule Statement
  * ----------------------
  */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index faea991..3abb90d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -85,6 +85,7 @@ PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
 PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD)
+PG_KEYWORD("commutator", COMMUTATOR, UNRESERVED_KEYWORD)
 PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD)
 PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD)
@@ -247,6 +248,7 @@ PG_KEYWORD("names", NAMES, UNRESERVED_KEYWORD)
 PG_KEYWORD("national", NATIONAL, COL_NAME_KEYWORD)
 PG_KEYWORD("natural", NATURAL, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("nchar", NCHAR, COL_NAME_KEYWORD)
+PG_KEYWORD("negator", NEGATOR, UNRESERVED_KEYWORD)
 PG_KEYWORD("next", NEXT, UNRESERVED_KEYWORD)
 PG_KEYWORD("no", NO, UNRESERVED_KEYWORD)
 PG_KEYWORD("none", NONE, COL_NAME_KEYWORD)
diff --git a/src/test/regress/expected/alter_operator.out b/src/test/regress/expected/alter_operator.out
new file mode 100644
index 0000000..9282ae0
--- /dev/null
+++ b/src/test/regress/expected/alter_operator.out
@@ -0,0 +1,117 @@
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+    SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = fn_op2,
+    COMMUTATOR = ===,
+    NEGATOR = !==,
+    RESTRICT = contsel,
+    JOIN = contjoinsel,
+    SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
+);
+--
+-- Reset and set params
+--
+ALTER OPERATOR === (boolean, boolean) SET COMMUTATOR NONE;
+ALTER OPERATOR === (boolean, boolean) SET NEGATOR NONE;
+SELECT oprcom, oprnegate FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprcom | oprnegate 
+--------+-----------
+      0 |         0
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET COMMUTATOR !=;
+ALTER OPERATOR === (boolean, boolean) SET NEGATOR =;
+SELECT oprcom, oprnegate FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprcom | oprnegate 
+--------+-----------
+     85 |        91
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET RESTRICT NONE;
+ALTER OPERATOR === (boolean, boolean) SET JOIN NONE;
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ 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 = 16 AND oprright = 16;
+ oprrest |   oprjoin   
+---------+-------------
+ contsel | contjoinsel
+(1 row)
+
+--
+-- Trying set the wrong parameters
+--
+ALTER OPERATOR === (boolean, boolean) SET COMMUTATOR ====;
+ERROR:  Not found function or operator for alter operator
+ALTER OPERATOR === (boolean, boolean) SET NEGATOR ====;
+ERROR:  Not found function or operator for alter operator
+ALTER OPERATOR === (boolean, boolean) SET RESTRICT blabla;
+ERROR:  function blabla(internal, oid, internal, integer) does not exist
+ALTER OPERATOR === (boolean, boolean) SET JOIN blabla;
+ERROR:  function blabla(internal, oid, internal, smallint, internal) does not exist
+ALTER OPERATOR === (boolean, boolean) SET COMMUTATOR !==;
+ERROR:  You can't set shell (fake) operator
+ALTER OPERATOR === (boolean, boolean) SET NEGATOR !==;
+ERROR:  You can't set shell (fake) operator
+--
+-- Trying set params from wrong user
+--
+CREATE USER regression_user;
+SET SESSION AUTHORIZATION regression_user;
+ALTER OPERATOR === (boolean, boolean) SET COMMUTATOR NONE;
+ERROR:  must be owner of operator ===
+ALTER OPERATOR === (boolean, boolean) SET NEGATOR NONE;
+ERROR:  must be owner of operator ===
+ALTER OPERATOR === (boolean, boolean) SET RESTRICT NONE;
+ERROR:  must be owner of operator ===
+ALTER OPERATOR === (boolean, boolean) SET JOIN NONE;
+ERROR:  must be owner of operator ===
+RESET SESSION AUTHORIZATION;
+DROP USER regression_user;
+DROP OPERATOR === (boolean, boolean);
+--
+-- Trying commutator work
+--
+CREATE TABLE test_ints(i int4);
+CREATE INDEX idx ON test_ints(i);
+INSERT INTO test_ints(i) VALUES (1);
+INSERT INTO test_ints(i) VALUES (2);
+INSERT INTO test_ints(i) VALUES (3);
+set enable_bitmapscan=off;
+set enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE 5::int4 > i;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx on test_ints
+   Index Cond: (i < 5)
+(2 rows)
+
+ALTER OPERATOR > (int4, int4) SET COMMUTATOR NONE;
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE 5::int4 > i;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx on test_ints
+   Filter: (5 > i)
+(2 rows)
+
+ALTER OPERATOR > (int4, int4) SET COMMUTATOR <;
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE 5::int4 > i;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx on test_ints
+   Index Cond: (i < 5)
+(2 rows)
+
+DROP TABLE test_ints;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index b0ebb6b..993f094 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml alter_operator
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 8409c0f..b52030c 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -145,6 +145,7 @@ test: without_oid
 test: conversion
 test: truncate
 test: alter_table
+test: alter_operator
 test: sequence
 test: polymorphism
 test: rowtypes
diff --git a/src/test/regress/sql/alter_operator.sql b/src/test/regress/sql/alter_operator.sql
new file mode 100644
index 0000000..4df74f5
--- /dev/null
+++ b/src/test/regress/sql/alter_operator.sql
@@ -0,0 +1,97 @@
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+    SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = fn_op2,
+    COMMUTATOR = ===,
+    NEGATOR = !==,
+    RESTRICT = contsel,
+    JOIN = contjoinsel,
+    SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
+);
+
+
+--
+-- Reset and set params
+--
+
+ALTER OPERATOR === (boolean, boolean) SET COMMUTATOR NONE;
+ALTER OPERATOR === (boolean, boolean) SET NEGATOR NONE;
+
+SELECT oprcom, oprnegate FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET COMMUTATOR !=;
+ALTER OPERATOR === (boolean, boolean) SET NEGATOR =;
+
+SELECT oprcom, oprnegate FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET RESTRICT NONE;
+ALTER OPERATOR === (boolean, boolean) SET JOIN NONE;
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET RESTRICT contsel;
+ALTER OPERATOR === (boolean, boolean) SET JOIN contjoinsel;
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+
+--
+-- Trying set the wrong parameters
+--
+
+ALTER OPERATOR === (boolean, boolean) SET COMMUTATOR ====;
+ALTER OPERATOR === (boolean, boolean) SET NEGATOR ====;
+ALTER OPERATOR === (boolean, boolean) SET RESTRICT blabla;
+ALTER OPERATOR === (boolean, boolean) SET JOIN blabla;
+ALTER OPERATOR === (boolean, boolean) SET COMMUTATOR !==;
+ALTER OPERATOR === (boolean, boolean) SET NEGATOR !==;
+
+
+--
+-- Trying set params from wrong user
+--
+
+CREATE USER regression_user;
+SET SESSION AUTHORIZATION regression_user;
+
+ALTER OPERATOR === (boolean, boolean) SET COMMUTATOR NONE;
+ALTER OPERATOR === (boolean, boolean) SET NEGATOR NONE;
+ALTER OPERATOR === (boolean, boolean) SET RESTRICT NONE;
+ALTER OPERATOR === (boolean, boolean) SET JOIN NONE;
+
+RESET SESSION AUTHORIZATION;
+DROP USER regression_user;
+
+DROP OPERATOR === (boolean, boolean);
+
+--
+-- Trying commutator work
+--
+
+CREATE TABLE test_ints(i int4);
+CREATE INDEX idx ON test_ints(i);
+INSERT INTO test_ints(i) VALUES (1);
+INSERT INTO test_ints(i) VALUES (2);
+INSERT INTO test_ints(i) VALUES (3);
+
+set enable_bitmapscan=off;
+set enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE 5::int4 > i;
+
+ALTER OPERATOR > (int4, int4) SET COMMUTATOR NONE;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE 5::int4 > i;
+
+ALTER OPERATOR > (int4, int4) SET COMMUTATOR <;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE 5::int4 > i;
+
+DROP TABLE test_ints;
\ No newline at end of file
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cfd580c..b67afb6 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -70,6 +70,7 @@ AlterFunctionStmt
 AlterObjectSchemaStmt
 AlterOpFamilyStmt
 AlterOwnerStmt
+AlterOperatorStmt
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Uriy Zhuravlev (#1)
Re: WIP: Enhanced ALTER OPERATOR

Uriy Zhuravlev <u.zhuravlev@postgrespro.ru> writes:

I have attached a patch that extends ALTER OPERATOR to support COMMUTATOR,
NEGATOR, RESTRICT and JOIN.

There are fairly significant reasons why we have not done this, based
on the difficulty of updating existing cached plans that might have
incidentally depended on those operator properties during creation.
Perhaps it's all right to simply ignore such concerns, but I would like
to see a defense of why.

On a more practical level, please change the syntax so that it does
not require making all those things into keywords.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Uriy Zhuravlev
u.zhuravlev@postgrespro.ru
In reply to: Tom Lane (#2)
Re: WIP: Enhanced ALTER OPERATOR

On Monday 18 May 2015 10:21:10 Tom Lane wrote:

There are fairly significant reasons why we have not done this, based
on the difficulty of updating existing cached plans that might have
incidentally depended on those operator properties during creation.
Perhaps it's all right to simply ignore such concerns, but I would like
to see a defense of why.

Then need to prohibit the use of shells operators (stub operators) to create
self-linkage. Implicitly changing commutators and negators working for a long
time through the shell operators.

On a more practical level, please change the syntax so that it does
not require making all those things into keywords.

Do you say about CREATE OPERATOR like syntax?

Thanks.

--
Uriy Zhuravlev
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Uriy Zhuravlev (#3)
Re: WIP: Enhanced ALTER OPERATOR

On Mon, May 18, 2015 at 5:44 PM, Uriy Zhuravlev <u.zhuravlev@postgrespro.ru>
wrote:

On Monday 18 May 2015 10:21:10 Tom Lane wrote:

There are fairly significant reasons why we have not done this, based
on the difficulty of updating existing cached plans that might have
incidentally depended on those operator properties during creation.
Perhaps it's all right to simply ignore such concerns, but I would like
to see a defense of why.

Then need to prohibit the use of shells operators (stub operators) to
create
self-linkage. Implicitly changing commutators and negators working for a
long
time through the shell operators.

I could give another motivation. AFAICS, typically ALTER OPERATOR should
introduce enchancements. For instance, some version of extension didn't
have negator for operator. In the next version extension introduce such
negator. Or the same situation with selectivity estimation. If ALTER
OPERATOR introduce only enchancements then old plans could be not optimal
but they don't lead to invalid query answers. From this point of view cache
invalidation after ALTER OPERATOR is term of optimization. We could include
into the patch documentation statement about possible side effects with
cached query plans.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#5Uriy Zhuravlev
u.zhuravlev@postgrespro.ru
In reply to: Tom Lane (#2)
Re: WIP: Enhanced ALTER OPERATOR

On Monday 18 May 2015 10:21:10 you wrote:

difficulty of updating existing cached plans

Could you specify more precisely about some caches we talking about? PREPARE
working correctly:

CREATE TABLE test_ints(i int4);
CREATE TABLE
CREATE INDEX idx ON test_ints(i);
CREATE INDEX
set enable_bitmapscan=off;
SET
set enable_seqscan=off;
SET
PREPARE test_plan (int) AS
SELECT * FROM test_ints WHERE $1::int4 > i;
PREPARE
EXPLAIN (COSTS OFF)
EXECUTE test_plan(5);
QUERY PLAN
----------------------------------------
Index Only Scan using idx on test_ints
Index Cond: (i < 5)

ALTER OPERATOR > (int4, int4) SET COMMUTATOR NONE;
ALTER OPERATOR
EXPLAIN (COSTS OFF)
EXECUTE test_plan(5);
QUERY PLAN
----------------------------------------
Index Only Scan using idx on test_ints
Filter: (5 > i)

And can you explain more about the syntax?

Thanks.

--
Uriy Zhuravlev
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Uriy Zhuravlev (#5)
Re: WIP: Enhanced ALTER OPERATOR

Uriy Zhuravlev wrote:

And can you explain more about the syntax?

I think he means to treat COMMUTATOR etc like a generic element list,
i.e. don't define new keywords in kwlist.h/gram.y at all but rather pass
the names as strings (probably using a list of DefElem) and strcmp()
them in OperatorUpd() or something.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#6)
Re: WIP: Enhanced ALTER OPERATOR

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

Uriy Zhuravlev wrote:

And can you explain more about the syntax?

I think he means to treat COMMUTATOR etc like a generic element list,
i.e. don't define new keywords in kwlist.h/gram.y at all but rather pass
the names as strings (probably using a list of DefElem) and strcmp()
them in OperatorUpd() or something.

Yeah. If they aren't keywords in CREATE OPERATOR, I don't think they
should be in ALTER OPERATOR either. Indeed, the syntax of the two
commands probably ought to be similar.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Andres Freund
andres@anarazel.de
In reply to: Uriy Zhuravlev (#5)
Re: WIP: Enhanced ALTER OPERATOR

Hi,

On 2015-05-20 12:22:34 +0300, Uriy Zhuravlev wrote:

On Monday 18 May 2015 10:21:10 you wrote:

difficulty of updating existing cached plans

Could you specify more precisely about some caches we talking about? PREPARE
working correctly:

CREATE TABLE test_ints(i int4);
CREATE TABLE
CREATE INDEX idx ON test_ints(i);
CREATE INDEX
set enable_bitmapscan=off;
SET
set enable_seqscan=off;
SET
PREPARE test_plan (int) AS
SELECT * FROM test_ints WHERE $1::int4 > i;
PREPARE
EXPLAIN (COSTS OFF)
EXECUTE test_plan(5);
QUERY PLAN
----------------------------------------
Index Only Scan using idx on test_ints
Index Cond: (i < 5)

ALTER OPERATOR > (int4, int4) SET COMMUTATOR NONE;
ALTER OPERATOR
EXPLAIN (COSTS OFF)
EXECUTE test_plan(5);
QUERY PLAN
----------------------------------------
Index Only Scan using idx on test_ints
Filter: (5 > i)

Note that this very likely wasn't actually using a prepared plan. Due to
the custom plan infrastructure the first few invocations are going to be
replanned.

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Uriy Zhuravlev
u.zhuravlev@postgrespro.ru
In reply to: Andres Freund (#8)
Re: WIP: Enhanced ALTER OPERATOR

On Wednesday 20 May 2015 20:50:41 Andres Freund wrote:

Note that this very likely wasn't actually using a prepared plan. Due to
the custom plan infrastructure the first few invocations are going to be
replanned.

Hello. I tested it on 30 and 50 iterations, and it feels good.

Thanks.
--
Uriy Zhuravlev
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Uriy Zhuravlev
u.zhuravlev@postgrespro.ru
In reply to: Uriy Zhuravlev (#1)
1 attachment(s)
Re: WIP: Enhanced ALTER OPERATOR

Hello Hackers.

I reworked the patch. Now, the syntax is similar to CREATE OPERATOR.
And as I wrote earlier problems with the cache I have not found. If someone
can suggest how it could be verified that would be happy.

New syntax example:
ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = =);
ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = =);
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = example_func);
ALTER OPERATOR === (boolean, boolean) SET (JOIN = example_func);

ALTER OPERATOR === (boolean, boolean) SET (
COMMUTATOR = NULL,
NEGATOR = NULL,
RESTRICT = NULL,
JOIN = NULL
);

Thanks!

--
Uriy Zhuravlev
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

extend_alter_operator_v2.patchtext/x-patch; charset=UTF-8; name=extend_alter_operator_v2.patchDownload
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 072f530..83e020b 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -28,8 +28,10 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/defrem.h"
 #include "miscadmin.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_func.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -53,7 +55,7 @@ static Oid OperatorShellMake(const char *operatorName,
 				  Oid leftTypeId,
 				  Oid rightTypeId);
 
-static void OperatorUpd(Oid baseId, Oid commId, Oid negId);
+static void ShellOperatorUpd(Oid baseId, Oid commId, Oid negId);
 
 static Oid get_other_operator(List *otherOp,
 				   Oid otherLeftTypeId, Oid otherRightTypeId,
@@ -563,7 +565,7 @@ OperatorCreate(const char *operatorName,
 		commutatorId = operatorObjectId;
 
 	if (OidIsValid(commutatorId) || OidIsValid(negatorId))
-		OperatorUpd(operatorObjectId, commutatorId, negatorId);
+		ShellOperatorUpd(operatorObjectId, commutatorId, negatorId);
 
 	return address;
 }
@@ -633,7 +635,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
 }
 
 /*
- * OperatorUpd
+ * ShellOperatorUpd
  *
  *	For a given operator, look up its negator and commutator operators.
  *	If they are defined, but their negator and commutator fields
@@ -642,7 +644,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
  *	which are the negator or commutator of each other.
  */
 static void
-OperatorUpd(Oid baseId, Oid commId, Oid negId)
+ShellOperatorUpd(Oid baseId, Oid commId, Oid negId)
 {
 	int			i;
 	Relation	pg_operator_desc;
@@ -864,3 +866,187 @@ makeOperatorDependencies(HeapTuple tuple)
 
 	return myself;
 }
+
+/*
+ * Operator update aka ALTER OPERATOR for COMMUTATOR, NEGATOR, RESTRICT, JOIN
+ */
+void OperatorUpd(Oid classId,
+				 Oid baseId,
+				 List *operator_params)
+{
+	int			i;
+	ListCell	*pl;
+	Relation	catalog;
+	HeapTuple	tup;
+	Oid 		operator_param_id = 0;
+	Form_pg_operator DstOperatorData;
+	bool		otherDefined;
+	bool		nulls[Natts_pg_operator];
+	bool		replaces[Natts_pg_operator];
+	Datum		values[Natts_pg_operator];
+
+	for (i = 0; i < Natts_pg_operator; ++i)
+	{
+		values[i] = (Datum) 0;
+		replaces[i] = false;
+		nulls[i] = false;
+	}
+
+	catalog = heap_open(classId, RowExclusiveLock);
+	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(baseId));
+	if (HeapTupleIsValid(tup))
+	{
+		DstOperatorData = (Form_pg_operator) GETSTRUCT(tup);
+
+		/*
+		 * loop over the definition list and extract the information we need.
+		 */
+		foreach(pl, operator_params)
+		{
+			DefElem    *defel = (DefElem *) lfirst(pl);
+			List	   *param = defGetQualifiedName(defel);
+			int			param_type;
+
+			if (pg_strcasecmp(defel->defname, "commutator") == 0)
+				param_type = Anum_pg_operator_oprcom;
+			else if (pg_strcasecmp(defel->defname, "negator") == 0)
+				param_type = Anum_pg_operator_oprnegate;
+			else if (pg_strcasecmp(defel->defname, "restrict") == 0)
+				param_type = Anum_pg_operator_oprrest;
+			else if (pg_strcasecmp(defel->defname, "join") == 0)
+				param_type = Anum_pg_operator_oprjoin;
+			else
+			{
+				ereport(WARNING,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("operator attribute \"%s\" not recognized",
+								defel->defname)));
+				continue;
+			}
+
+			/* Resets if written NULL */
+			if (pg_strcasecmp(NameListToString(param), "null") == 0)
+			{
+				values[param_type - 1] = ObjectIdGetDatum(InvalidOid);
+				replaces[param_type - 1] = true;
+				continue;
+			}
+
+			/*
+			 * Prepare tuple to upgrade the operator
+			 * considering the type of the parameter.
+			 */
+			if (param_type == Anum_pg_operator_oprcom ||
+				param_type == Anum_pg_operator_oprnegate)
+			{
+				otherDefined = true;
+				if (PointerIsValid(param))
+					operator_param_id = OperatorLookup(param,
+												DstOperatorData->oprleft,
+												DstOperatorData->oprright,
+												&otherDefined);
+				else
+					operator_param_id = InvalidOid;
+
+				if (!otherDefined && OidIsValid(operator_param_id)) {
+					ereport(ERROR,
+						(errmsg_internal("You can't set shell (fake) operator")));
+				}
+			}
+			else if (param_type == Anum_pg_operator_oprrest)
+			{
+				if (PointerIsValid(param))
+				{
+					Oid			typeId[5];
+					AclResult	aclresult;
+					typeId[0] = INTERNALOID;	/* PlannerInfo */
+					typeId[1] = OIDOID;		/* operator OID */
+					typeId[2] = INTERNALOID;	/* args list */
+					typeId[3] = INT4OID;	/* varRelid */
+
+					operator_param_id = LookupFuncName(param, 4, typeId, false);
+
+					/* estimators must return float8 */
+					if (get_func_rettype(operator_param_id) != FLOAT8OID)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("restriction estimator function %s must return type \"float8\"",
+										NameListToString(param))));
+
+					/* Require EXECUTE rights for the estimator */
+					aclresult = pg_proc_aclcheck(operator_param_id, GetUserId(), ACL_EXECUTE);
+					if (aclresult != ACLCHECK_OK)
+						aclcheck_error(aclresult, ACL_KIND_PROC,
+									   NameListToString(param));
+				}
+				else
+					operator_param_id = 0;
+			}
+			else if (param_type == Anum_pg_operator_oprjoin)
+			{
+				if (PointerIsValid(param))
+				{
+					Oid			typeId[5];
+					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.
+					 */
+					operator_param_id = LookupFuncName(param, 5, typeId, true);
+					if (!OidIsValid(operator_param_id))
+						operator_param_id = LookupFuncName(param, 4, typeId, true);
+					/* If not found, reference the 5-argument signature in error msg */
+					if (!OidIsValid(operator_param_id))
+						operator_param_id = LookupFuncName(param, 5, typeId, false);
+
+					/* estimators must return float8 */
+					if (get_func_rettype(operator_param_id) != FLOAT8OID)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("restriction estimator function %s must return type \"float8\"",
+										NameListToString(param))));
+
+					/* Require EXECUTE rights for the estimator */
+					aclresult = pg_proc_aclcheck(operator_param_id, GetUserId(), ACL_EXECUTE);
+					if (aclresult != ACLCHECK_OK)
+						aclcheck_error(aclresult, ACL_KIND_PROC,
+									   NameListToString(param));
+				}
+				else
+					operator_param_id = 0;
+			}
+
+			if (OidIsValid(operator_param_id))
+			{
+				values[param_type - 1] = ObjectIdGetDatum(operator_param_id);
+				replaces[param_type - 1] = true;
+			}
+			else
+				ereport(ERROR,
+					(errmsg_internal("Not found function or operator for alter operator")));
+		}
+
+		/* Update heap */
+		tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(baseId));
+		if (HeapTupleIsValid(tup))
+		{
+			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);
+}
\ No newline at end of file
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index b4a1aac..43e0d66 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -320,3 +320,74 @@ RemoveOperatorById(Oid operOid)
 
 	heap_close(relation, RowExclusiveLock);
 }
+
+ObjectAddress
+ExecAlterOperatorStmt(AlterOperatorStmt *stmt)
+{
+	ObjectAddress address;
+	Relation	catalog;
+	Relation	relation;
+	Datum		datum;
+	Oid			ownerId;
+	bool		isnull;
+	HeapTuple	tup;
+
+	/* Address to be modified operator. */
+	address = get_object_address(OBJECT_OPERATOR,
+								 stmt->object,
+								 stmt->objarg,
+								 &relation,
+								 AccessExclusiveLock,
+								 false);
+	Assert(relation == NULL);
+
+	/* Check user rights. */
+	if (!superuser())
+	{
+		AclObjectKind 	aclkind = get_object_aclkind(address.classId);
+		AttrNumber		Anum_name = get_object_attnum_name(address.classId);
+		AttrNumber		Anum_owner = get_object_attnum_owner(address.classId);;
+
+		catalog = heap_open(address.classId, RowExclusiveLock);
+
+		tup = get_catalog_object_by_oid(catalog, address.objectId);
+
+		if (tup == NULL)
+			elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+				 address.objectId, RelationGetRelationName(catalog));
+
+		datum = heap_getattr(tup, Anum_owner,
+							 RelationGetDescr(catalog), &isnull);
+		Assert(!isnull);
+		ownerId = DatumGetObjectId(datum);
+
+		/* must be owner */
+		if (!has_privs_of_role(GetUserId(), ownerId))
+		{
+			char	   *objname;
+			char		namebuf[NAMEDATALEN];
+
+			if (Anum_name != InvalidAttrNumber)
+			{
+				datum = heap_getattr(tup, Anum_name,
+									 RelationGetDescr(catalog), &isnull);
+				Assert(!isnull);
+				objname = NameStr(*DatumGetName(datum));
+			}
+			else
+			{
+				snprintf(namebuf, sizeof(namebuf), "%u",
+						 HeapTupleGetOid(tup));
+				objname = namebuf;
+			}
+			aclcheck_error(ACLCHECK_NOT_OWNER, aclkind, objname);
+		}
+		heap_close(catalog, RowExclusiveLock);
+	}
+
+	OperatorUpd(address.classId,
+				address.objectId,
+				stmt->defnames);
+
+	return address;
+}
\ No newline at end of file
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2d9bf41..44e93b7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3213,6 +3213,19 @@ _copyAlterOwnerStmt(const AlterOwnerStmt *from)
 	return newnode;
 }
 
+static AlterOperatorStmt *
+_copyAlterOperatorStmt(const AlterOperatorStmt *from)
+{
+	AlterOperatorStmt *newnode = makeNode(AlterOperatorStmt);
+
+	COPY_NODE_FIELD(relation);
+	COPY_NODE_FIELD(object);
+	COPY_NODE_FIELD(objarg);
+	COPY_NODE_FIELD(defnames);
+
+	return newnode;
+}
+
 static RuleStmt *
 _copyRuleStmt(const RuleStmt *from)
 {
@@ -4616,6 +4629,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 f19251e..017891a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1339,6 +1339,17 @@ _equalAlterOwnerStmt(const AlterOwnerStmt *a, const AlterOwnerStmt *b)
 }
 
 static bool
+_equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b)
+{
+	COMPARE_NODE_FIELD(relation);
+	COMPARE_NODE_FIELD(object);
+	COMPARE_NODE_FIELD(objarg);
+	COMPARE_NODE_FIELD(defnames);
+
+	return true;
+}
+
+static bool
 _equalRuleStmt(const RuleStmt *a, const RuleStmt *b)
 {
 	COMPARE_NODE_FIELD(relation);
@@ -2980,6 +2991,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 db6c6f7..3a22717 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
@@ -769,6 +769,7 @@ stmt :
 			| AlterGroupStmt
 			| AlterObjectSchemaStmt
 			| AlterOwnerStmt
+			| AlterOperatorStmt
 			| AlterPolicyStmt
 			| AlterSeqStmt
 			| AlterSystemStmt
@@ -8198,6 +8199,24 @@ AlterObjectSchemaStmt:
 
 /*****************************************************************************
  *
+ * ALTER OPERATOR name SET define
+ *
+ *****************************************************************************/
+
+AlterOperatorStmt:
+			ALTER OPERATOR any_operator oper_argtypes SET definition
+				{
+					AlterOperatorStmt *n = makeNode(AlterOperatorStmt);
+					n->object = $3;
+					n->objarg = $4;
+					n->defnames = $6;
+					$$ = (Node *)n;
+				}
+		;
+
+
+/*****************************************************************************
+ *
  * ALTER THING name OWNER TO newname
  *
  *****************************************************************************/
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index a95eff1..712bb51 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:
@@ -861,6 +862,19 @@ standard_ProcessUtility(Node *parsetree,
 			}
 			break;
 
+		case T_AlterOperatorStmt:
+			{
+				AlterOperatorStmt *stmt = (AlterOperatorStmt *) parsetree;
+				if (EventTriggerSupportsObjectType(OBJECT_OPERATOR)) {
+					ProcessUtilitySlow(parsetree, queryString,
+									   context, params,
+									   dest, completionTag);
+				}
+				else
+					ExecAlterOperatorStmt(stmt);
+			}
+			break;
+
 		case T_CommentStmt:
 			{
 				CommentStmt *stmt = (CommentStmt *) parsetree;
@@ -916,12 +930,14 @@ ProcessUtilitySlow(Node *parsetree,
 	ObjectAddress address;
 	ObjectAddress secondaryObject = InvalidObjectAddress;
 
+
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
 
 	/* PG_TRY block is to ensure we call EventTriggerEndCompleteQuery */
 	PG_TRY();
 	{
+
 		if (isCompleteQuery)
 			EventTriggerDDLCommandStart(parsetree);
 
@@ -1478,6 +1494,10 @@ ProcessUtilitySlow(Node *parsetree,
 				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_AlterOperatorStmt:
+				address = ExecAlterOperatorStmt((AlterOperatorStmt *) parsetree);
+				break;
+
 			case T_CommentStmt:
 				address = CommentObject((CommentStmt *) parsetree);
 				break;
@@ -2491,6 +2511,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/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 6e260cb..b5c3040 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1837,4 +1837,8 @@ extern ObjectAddress OperatorCreate(const char *operatorName,
 			   bool canMerge,
 			   bool canHash);
 
+extern void OperatorUpd(Oid classId,
+		Oid baseId,
+		List *operator_params);
+
 #endif   /* PG_OPERATOR_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index d625725..221e094 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 ExecAlterOperatorStmt(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 669a0af..b9e9224 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 23190e1..ec4cf2c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2543,6 +2543,20 @@ typedef struct AlterOwnerStmt
 
 
 /* ----------------------
+ *		Alter Operator Set Commutator, Negator, Restrict, Join
+ * ----------------------
+ */
+typedef struct AlterOperatorStmt
+{
+	NodeTag		type;
+	RangeVar   *relation;		/* in case it's a table */
+	List	   *object;			/* in case it's some other object */
+	List	   *objarg;			/* argument types, if applicable */
+	List	   *defnames;		/* operator */
+} 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 0000000..16438e0
--- /dev/null
+++ b/src/test/regress/expected/alter_operator.out
@@ -0,0 +1,161 @@
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+    SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = fn_op2,
+    COMMUTATOR = ===,
+    NEGATOR = !==,
+    RESTRICT = contsel,
+    JOIN = contjoinsel,
+    SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
+);
+--
+-- Reset and set params
+--
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = NULL);
+SELECT oprcom, oprnegate FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprcom | oprnegate 
+--------+-----------
+      0 |         0
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !=);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = =);
+SELECT oprcom, oprnegate FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprcom | oprnegate 
+--------+-----------
+     85 |        91
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ 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 = 16 AND oprright = 16;
+ oprrest |   oprjoin   
+---------+-------------
+ contsel | contjoinsel
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = NULL,
+										   NEGATOR = NULL,
+										   RESTRICT = NULL,
+										   JOIN = NULL);
+SELECT oprcom, oprnegate, oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprcom | oprnegate | oprrest | oprjoin 
+--------+-----------+---------+---------
+      0 |         0 | -       | -
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !=,
+										   NEGATOR = =,
+										   RESTRICT = contsel,
+										   JOIN = contjoinsel);
+SELECT oprcom, oprnegate, oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprcom | oprnegate | oprrest |   oprjoin   
+--------+-----------+---------+-------------
+     85 |        91 | contsel | contjoinsel
+(1 row)
+
+--
+-- Trying set the wrong parameters
+--
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+ERROR:  Not found function or operator for alter operator
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+ERROR:  Not found function or operator for alter operator
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = blabla);
+ERROR:  function blabla(internal, oid, internal, integer) does not exist
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = blabla);
+ERROR:  function blabla(internal, oid, internal, smallint, internal) does not exist
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
+ERROR:  You can't set shell (fake) operator
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+ERROR:  You can't set shell (fake) operator
+--
+-- Trying set params from wrong user
+--
+CREATE USER regression_user;
+SET SESSION AUTHORIZATION regression_user;
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = NULL);
+ERROR:  must be owner of operator ===
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = NULL);
+ERROR:  must be owner of operator ===
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ERROR:  must be owner of operator ===
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+ERROR:  must be owner of operator ===
+RESET SESSION AUTHORIZATION;
+DROP USER regression_user;
+DROP OPERATOR === (boolean, boolean);
+--
+-- Trying commutator and negator work
+--
+CREATE TABLE test_ints(i int4);
+CREATE INDEX idx ON test_ints(i);
+INSERT INTO test_ints(i) VALUES (1);
+INSERT INTO test_ints(i) VALUES (2);
+INSERT INTO test_ints(i) VALUES (3);
+set enable_bitmapscan=off;
+set enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE 5::int4 > i;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx on test_ints
+   Index Cond: (i < 5)
+(2 rows)
+
+ALTER OPERATOR > (int4, int4) SET (COMMUTATOR = NULL);
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE 5::int4 > i;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx on test_ints
+   Filter: (5 > i)
+(2 rows)
+
+ALTER OPERATOR > (int4, int4) SET (COMMUTATOR = <);
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE 5::int4 > i;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx on test_ints
+   Index Cond: (i < 5)
+(2 rows)
+
+ALTER OPERATOR > (int4, int4) SET (COMMUTATOR = NULL, NEGATOR = NULL);
+-- Negator test
+DROP INDEX idx;
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE NOT (5::int4 > i);
+       QUERY PLAN        
+-------------------------
+ Seq Scan on test_ints
+   Filter: (NOT (5 > i))
+(2 rows)
+
+ALTER OPERATOR > (int4, int4) SET (NEGATOR = <=);
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE NOT (5::int4 > i);
+      QUERY PLAN       
+-----------------------
+ Seq Scan on test_ints
+   Filter: (5 <= i)
+(2 rows)
+
+-- Revert commutator for >
+ALTER OPERATOR > (int4, int4) SET (COMMUTATOR = <);
+DROP TABLE test_ints;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 91780cd..836f9f9 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml alter_operator
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index a2e0ceb..fbec844 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -146,6 +146,7 @@ test: without_oid
 test: conversion
 test: truncate
 test: alter_table
+test: alter_operator
 test: sequence
 test: polymorphism
 test: rowtypes
diff --git a/src/test/regress/sql/alter_operator.sql b/src/test/regress/sql/alter_operator.sql
new file mode 100644
index 0000000..e6063b5
--- /dev/null
+++ b/src/test/regress/sql/alter_operator.sql
@@ -0,0 +1,130 @@
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+    SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = fn_op2,
+    COMMUTATOR = ===,
+    NEGATOR = !==,
+    RESTRICT = contsel,
+    JOIN = contjoinsel,
+    SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
+);
+
+
+--
+-- Reset and set params
+--
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = NULL);
+
+SELECT oprcom, oprnegate FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !=);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = =);
+
+SELECT oprcom, oprnegate FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = NULL,
+										   NEGATOR = NULL,
+										   RESTRICT = NULL,
+										   JOIN = NULL);
+
+SELECT oprcom, oprnegate, oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !=,
+										   NEGATOR = =,
+										   RESTRICT = contsel,
+										   JOIN = contjoinsel);
+
+SELECT oprcom, oprnegate, oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+--
+-- Trying set the wrong parameters
+--
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = blabla);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = blabla);
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+
+
+--
+-- Trying set params from wrong user
+--
+
+CREATE USER regression_user;
+SET SESSION AUTHORIZATION regression_user;
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+
+RESET SESSION AUTHORIZATION;
+DROP USER regression_user;
+
+DROP OPERATOR === (boolean, boolean);
+
+--
+-- Trying commutator and negator work
+--
+
+CREATE TABLE test_ints(i int4);
+CREATE INDEX idx ON test_ints(i);
+INSERT INTO test_ints(i) VALUES (1);
+INSERT INTO test_ints(i) VALUES (2);
+INSERT INTO test_ints(i) VALUES (3);
+
+set enable_bitmapscan=off;
+set enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE 5::int4 > i;
+
+ALTER OPERATOR > (int4, int4) SET (COMMUTATOR = NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE 5::int4 > i;
+
+ALTER OPERATOR > (int4, int4) SET (COMMUTATOR = <);
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE 5::int4 > i;
+
+ALTER OPERATOR > (int4, int4) SET (COMMUTATOR = NULL, NEGATOR = NULL);
+
+-- Negator test
+
+DROP INDEX idx;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE NOT (5::int4 > i);
+
+ALTER OPERATOR > (int4, int4) SET (NEGATOR = <=);
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_ints WHERE NOT (5::int4 > i);
+
+-- Revert commutator for >
+
+ALTER OPERATOR > (int4, int4) SET (COMMUTATOR = <);
+
+DROP TABLE test_ints;
\ No newline at end of file
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cfd580c..b67afb6 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -70,6 +70,7 @@ AlterFunctionStmt
 AlterObjectSchemaStmt
 AlterOpFamilyStmt
 AlterOwnerStmt
+AlterOperatorStmt
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
#11Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#2)
Re: WIP: Enhanced ALTER OPERATOR

On Mon, May 18, 2015 at 10:21 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Uriy Zhuravlev <u.zhuravlev@postgrespro.ru> writes:

I have attached a patch that extends ALTER OPERATOR to support COMMUTATOR,
NEGATOR, RESTRICT and JOIN.

There are fairly significant reasons why we have not done this, based
on the difficulty of updating existing cached plans that might have
incidentally depended on those operator properties during creation.
Perhaps it's all right to simply ignore such concerns, but I would like
to see a defense of why.

I don't think there's a direct problem with cached plans, because it
looks like plancache.c blows away the entire plan cache for any change
to pg_operator. OperatorUpd() can already update oprcom and
oprnegate, which seems to stand for the proposition that it's OK to
set those from InvalidOid to something else. But that doesn't prove
that other kinds of changes are safe.

A search of other places where oprcom is used in the code led me to
ComputeIndexAttrs(). If an operator whose commutator is itself were
changed so that the commutator was anything else, then we'd end up
with a broken exclusion constraint. That's a problem.
targetIsInSortList is run during parse analysis, and that too tests
whether sortop == get_commutator(scl->sortop). Those decisions
wouldn't get reevaluated if the truth of that expression changed after
the fact, which I suspect is also a problem.

On a quick survey, I did not find similar hazards related to
oprnegate, oprrest, or oprjoin. AFAICS, they are used only by the
planner. I *think* that means that if our plan invalidation code is
smart enough, those instances will be OK. But I haven't audited them
in detail.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Robert Haas (#11)
Re: WIP: Enhanced ALTER OPERATOR

On Wed, May 27, 2015 at 9:28 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, May 18, 2015 at 10:21 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Uriy Zhuravlev <u.zhuravlev@postgrespro.ru> writes:

I have attached a patch that extends ALTER OPERATOR to support

COMMUTATOR,

NEGATOR, RESTRICT and JOIN.

There are fairly significant reasons why we have not done this, based
on the difficulty of updating existing cached plans that might have
incidentally depended on those operator properties during creation.
Perhaps it's all right to simply ignore such concerns, but I would like
to see a defense of why.

I don't think there's a direct problem with cached plans, because it
looks like plancache.c blows away the entire plan cache for any change
to pg_operator. OperatorUpd() can already update oprcom and
oprnegate, which seems to stand for the proposition that it's OK to
set those from InvalidOid to something else. But that doesn't prove
that other kinds of changes are safe.

A search of other places where oprcom is used in the code led me to
ComputeIndexAttrs(). If an operator whose commutator is itself were
changed so that the commutator was anything else, then we'd end up
with a broken exclusion constraint. That's a problem.
targetIsInSortList is run during parse analysis, and that too tests
whether sortop == get_commutator(scl->sortop). Those decisions
wouldn't get reevaluated if the truth of that expression changed after
the fact, which I suspect is also a problem.

Could we address both this problems by denying changing existing
commutators and negator? ISTM that setting absent commutator and negator is
quite enough for ALTER OPERATOR. User extensions could need setting of
commutator and negator because they could add new operators which don't
exist before. But it's rather uncommon to unset or change commutator or
negator.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#13Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alexander Korotkov (#12)
Re: WIP: Enhanced ALTER OPERATOR

Alexander Korotkov <a.korotkov@postgrespro.ru> writes:

Could we address both this problems by denying changing existing
commutators and negator? ISTM that setting absent commutator and negator is
quite enough for ALTER OPERATOR. User extensions could need setting of
commutator and negator because they could add new operators which don't
exist before. But it's rather uncommon to unset or change commutator or
negator.

Note that this functionality is already covered, in that you can specify
the commutator/negator linkage when you create the second operator.
I'm not particularly convinced that we need to have it in ALTER OPERATOR.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Tom Lane (#13)
Re: WIP: Enhanced ALTER OPERATOR

On Thu, May 28, 2015 at 6:43 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Alexander Korotkov <a.korotkov@postgrespro.ru> writes:

Could we address both this problems by denying changing existing
commutators and negator? ISTM that setting absent commutator and negator

is

quite enough for ALTER OPERATOR. User extensions could need setting of
commutator and negator because they could add new operators which don't
exist before. But it's rather uncommon to unset or change commutator or
negator.

Note that this functionality is already covered, in that you can specify
the commutator/negator linkage when you create the second operator.
I'm not particularly convinced that we need to have it in ALTER OPERATOR.

Yeah, I didn't notice that CREATE OPERATOR sets commutator and negator on
opposite side as well. Then we probably can leave ALTER OPERATOR without
altering commutator and negator.

BTW, does DROP OPERATOR works correctly?

# create operator == (procedure = int8eq, leftarg = bigint, rightarg =
bigint);
CREATE OPERATOR
# create operator !== (procedure = int8ne, leftarg = bigint, rightarg =
bigint, negator = ==);
CREATE OPERATOR
# select oid, * from pg_operator where oprnamespace = (select oid from
pg_namespace where nspname = 'public');
oid | oprname | oprnamespace | oprowner | oprkind | oprcanmerge |
oprcanhash | oprleft | oprright | oprresult | oprcom | oprnegate | oprcode
| oprrest | oprjoin
-------+---------+--------------+----------+---------+-------------+------------+---------+----------+-----------+--------+-----------+---------+---------+---------
46355 | !== | 2200 | 10 | b | f | f
| 20 | 20 | 16 | 0 | 46354 | int8ne | -
| -
46354 | == | 2200 | 10 | b | f | f
| 20 | 20 | 16 | 0 | 46355 | int8eq | -
| -
(2 rows)
# create table test (id bigint);
CREATE TABLE
# explain verbose select * from test where not id == 10::bigint;
QUERY PLAN
---------------------------------------------------------------
Seq Scan on public.test (cost=0.00..38.25 rows=1130 width=8)
Output: id
Filter: (test.id !== '10'::bigint)
(3 rows)
# drop operator !== (int8, int8);
DROP OPERATOR
# select oid, * from pg_operator where oprnamespace = (select oid from
pg_namespace where nspname = 'public');
oid | oprname | oprnamespace | oprowner | oprkind | oprcanmerge |
oprcanhash | oprleft | oprright | oprresult | oprcom | oprnegate | oprcode
| oprrest | oprjoin
-------+---------+--------------+----------+---------+-------------+------------+---------+----------+-----------+--------+-----------+---------+---------+---------
46354 | == | 2200 | 10 | b | f | f
| 20 | 20 | 16 | 0 | 46355 | int8eq | -
| -
(1 row)
# explain verbose select * from test where not id == 10::bigint;
ERROR: cache lookup failed for function 0

DROP OPERATOR leaves broken reference in oprnegate. Should we fix it?

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#15Robert Haas
robertmhaas@gmail.com
In reply to: Alexander Korotkov (#14)
Re: WIP: Enhanced ALTER OPERATOR

On Fri, May 29, 2015 at 4:28 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Thu, May 28, 2015 at 6:43 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Alexander Korotkov <a.korotkov@postgrespro.ru> writes:

Could we address both this problems by denying changing existing
commutators and negator? ISTM that setting absent commutator and negator
is
quite enough for ALTER OPERATOR. User extensions could need setting of
commutator and negator because they could add new operators which don't
exist before. But it's rather uncommon to unset or change commutator or
negator.

Note that this functionality is already covered, in that you can specify
the commutator/negator linkage when you create the second operator.
I'm not particularly convinced that we need to have it in ALTER OPERATOR.

Yeah, I didn't notice that CREATE OPERATOR sets commutator and negator on
opposite side as well. Then we probably can leave ALTER OPERATOR without
altering commutator and negator.

BTW, does DROP OPERATOR works correctly?

# create operator == (procedure = int8eq, leftarg = bigint, rightarg =
bigint);
CREATE OPERATOR
# create operator !== (procedure = int8ne, leftarg = bigint, rightarg =
bigint, negator = ==);
CREATE OPERATOR
# select oid, * from pg_operator where oprnamespace = (select oid from
pg_namespace where nspname = 'public');
oid | oprname | oprnamespace | oprowner | oprkind | oprcanmerge |
oprcanhash | oprleft | oprright | oprresult | oprcom | oprnegate | oprcode |
oprrest | oprjoin
-------+---------+--------------+----------+---------+-------------+------------+---------+----------+-----------+--------+-----------+---------+---------+---------
46355 | !== | 2200 | 10 | b | f | f
| 20 | 20 | 16 | 0 | 46354 | int8ne | - |
-
46354 | == | 2200 | 10 | b | f | f
| 20 | 20 | 16 | 0 | 46355 | int8eq | - |
-
(2 rows)
# create table test (id bigint);
CREATE TABLE
# explain verbose select * from test where not id == 10::bigint;
QUERY PLAN
---------------------------------------------------------------
Seq Scan on public.test (cost=0.00..38.25 rows=1130 width=8)
Output: id
Filter: (test.id !== '10'::bigint)
(3 rows)
# drop operator !== (int8, int8);
DROP OPERATOR
# select oid, * from pg_operator where oprnamespace = (select oid from
pg_namespace where nspname = 'public');
oid | oprname | oprnamespace | oprowner | oprkind | oprcanmerge |
oprcanhash | oprleft | oprright | oprresult | oprcom | oprnegate | oprcode |
oprrest | oprjoin
-------+---------+--------------+----------+---------+-------------+------------+---------+----------+-----------+--------+-----------+---------+---------+---------
46354 | == | 2200 | 10 | b | f | f
| 20 | 20 | 16 | 0 | 46355 | int8eq | - |
-
(1 row)
# explain verbose select * from test where not id == 10::bigint;
ERROR: cache lookup failed for function 0

DROP OPERATOR leaves broken reference in oprnegate. Should we fix it?

Yeah, that doesn't seem good.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Uriy Zhuravlev
u.zhuravlev@postgrespro.ru
In reply to: Uriy Zhuravlev (#1)
1 attachment(s)
Re: WIP: Enhanced ALTER OPERATOR

Hello Hackers.

Because change the commutator and negator raised questions I suggest 3 version
of the patch without them. In addition, for us now is much more important
restrict and join (for "Selectivity estimation for intarray" patch).

What do you think?

Thanks!

--
Uriy Zhuravlev
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

extend_alter_operator_v3.patchtext/x-patch; charset=UTF-8; name=extend_alter_operator_v3.patchDownload
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 072f530..f7770fd 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -28,8 +28,10 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/defrem.h"
 #include "miscadmin.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_func.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -53,7 +55,7 @@ static Oid OperatorShellMake(const char *operatorName,
 				  Oid leftTypeId,
 				  Oid rightTypeId);
 
-static void OperatorUpd(Oid baseId, Oid commId, Oid negId);
+static void ShellOperatorUpd(Oid baseId, Oid commId, Oid negId);
 
 static Oid get_other_operator(List *otherOp,
 				   Oid otherLeftTypeId, Oid otherRightTypeId,
@@ -563,7 +565,7 @@ OperatorCreate(const char *operatorName,
 		commutatorId = operatorObjectId;
 
 	if (OidIsValid(commutatorId) || OidIsValid(negatorId))
-		OperatorUpd(operatorObjectId, commutatorId, negatorId);
+		ShellOperatorUpd(operatorObjectId, commutatorId, negatorId);
 
 	return address;
 }
@@ -633,7 +635,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
 }
 
 /*
- * OperatorUpd
+ * ShellOperatorUpd
  *
  *	For a given operator, look up its negator and commutator operators.
  *	If they are defined, but their negator and commutator fields
@@ -642,7 +644,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
  *	which are the negator or commutator of each other.
  */
 static void
-OperatorUpd(Oid baseId, Oid commId, Oid negId)
+ShellOperatorUpd(Oid baseId, Oid commId, Oid negId)
 {
 	int			i;
 	Relation	pg_operator_desc;
@@ -864,3 +866,158 @@ makeOperatorDependencies(HeapTuple tuple)
 
 	return myself;
 }
+
+/*
+ * Operator update aka ALTER OPERATOR for RESTRICT, JOIN
+ */
+void OperatorUpd(Oid classId,
+				 Oid baseId,
+				 List *operator_params)
+{
+	int			i;
+	ListCell	*pl;
+	Relation	catalog;
+	HeapTuple	tup;
+	Oid 		operator_param_id = 0;
+	bool		nulls[Natts_pg_operator];
+	bool		replaces[Natts_pg_operator];
+	Datum		values[Natts_pg_operator];
+
+	for (i = 0; i < Natts_pg_operator; ++i)
+	{
+		values[i] = (Datum) 0;
+		replaces[i] = false;
+		nulls[i] = false;
+	}
+
+	catalog = heap_open(classId, RowExclusiveLock);
+	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(baseId));
+	if (HeapTupleIsValid(tup))
+	{
+		/*
+		 * loop over the definition list and extract the information we need.
+		 */
+		foreach(pl, operator_params)
+		{
+			DefElem    *defel = (DefElem *) lfirst(pl);
+			List	   *param = defGetQualifiedName(defel);
+			int			param_type;
+
+			if (pg_strcasecmp(defel->defname, "restrict") == 0)
+				param_type = Anum_pg_operator_oprrest;
+			else if (pg_strcasecmp(defel->defname, "join") == 0)
+				param_type = Anum_pg_operator_oprjoin;
+			else
+			{
+				ereport(WARNING,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("operator attribute \"%s\" not recognized",
+								defel->defname)));
+				continue;
+			}
+
+			/* Resets if written NULL */
+			if (pg_strcasecmp(NameListToString(param), "null") == 0)
+			{
+				values[param_type - 1] = ObjectIdGetDatum(InvalidOid);
+				replaces[param_type - 1] = true;
+				continue;
+			}
+
+			if (param_type == Anum_pg_operator_oprrest)
+			{
+				if (PointerIsValid(param))
+				{
+					Oid			typeId[5];
+					AclResult	aclresult;
+					typeId[0] = INTERNALOID;	/* PlannerInfo */
+					typeId[1] = OIDOID;		/* operator OID */
+					typeId[2] = INTERNALOID;	/* args list */
+					typeId[3] = INT4OID;	/* varRelid */
+
+					operator_param_id = LookupFuncName(param, 4, typeId, false);
+
+					/* estimators must return float8 */
+					if (get_func_rettype(operator_param_id) != FLOAT8OID)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("restriction estimator function %s must return type \"float8\"",
+										NameListToString(param))));
+
+					/* Require EXECUTE rights for the estimator */
+					aclresult = pg_proc_aclcheck(operator_param_id, GetUserId(), ACL_EXECUTE);
+					if (aclresult != ACLCHECK_OK)
+						aclcheck_error(aclresult, ACL_KIND_PROC,
+									   NameListToString(param));
+				}
+				else
+					operator_param_id = 0;
+			}
+			else if (param_type == Anum_pg_operator_oprjoin)
+			{
+				if (PointerIsValid(param))
+				{
+					Oid			typeId[5];
+					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.
+					 */
+					operator_param_id = LookupFuncName(param, 5, typeId, true);
+					if (!OidIsValid(operator_param_id))
+						operator_param_id = LookupFuncName(param, 4, typeId, true);
+					/* If not found, reference the 5-argument signature in error msg */
+					if (!OidIsValid(operator_param_id))
+						operator_param_id = LookupFuncName(param, 5, typeId, false);
+
+					/* estimators must return float8 */
+					if (get_func_rettype(operator_param_id) != FLOAT8OID)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("restriction estimator function %s must return type \"float8\"",
+										NameListToString(param))));
+
+					/* Require EXECUTE rights for the estimator */
+					aclresult = pg_proc_aclcheck(operator_param_id, GetUserId(), ACL_EXECUTE);
+					if (aclresult != ACLCHECK_OK)
+						aclcheck_error(aclresult, ACL_KIND_PROC,
+									   NameListToString(param));
+				}
+				else
+					operator_param_id = 0;
+			}
+
+			if (OidIsValid(operator_param_id))
+			{
+				values[param_type - 1] = ObjectIdGetDatum(operator_param_id);
+				replaces[param_type - 1] = true;
+			}
+			else
+				ereport(ERROR,
+					(errmsg_internal("Not found function or operator for alter operator")));
+		}
+
+		/* Update heap */
+		tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(baseId));
+		if (HeapTupleIsValid(tup))
+		{
+			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);
+}
\ No newline at end of file
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index b4a1aac..43e0d66 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -320,3 +320,74 @@ RemoveOperatorById(Oid operOid)
 
 	heap_close(relation, RowExclusiveLock);
 }
+
+ObjectAddress
+ExecAlterOperatorStmt(AlterOperatorStmt *stmt)
+{
+	ObjectAddress address;
+	Relation	catalog;
+	Relation	relation;
+	Datum		datum;
+	Oid			ownerId;
+	bool		isnull;
+	HeapTuple	tup;
+
+	/* Address to be modified operator. */
+	address = get_object_address(OBJECT_OPERATOR,
+								 stmt->object,
+								 stmt->objarg,
+								 &relation,
+								 AccessExclusiveLock,
+								 false);
+	Assert(relation == NULL);
+
+	/* Check user rights. */
+	if (!superuser())
+	{
+		AclObjectKind 	aclkind = get_object_aclkind(address.classId);
+		AttrNumber		Anum_name = get_object_attnum_name(address.classId);
+		AttrNumber		Anum_owner = get_object_attnum_owner(address.classId);;
+
+		catalog = heap_open(address.classId, RowExclusiveLock);
+
+		tup = get_catalog_object_by_oid(catalog, address.objectId);
+
+		if (tup == NULL)
+			elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+				 address.objectId, RelationGetRelationName(catalog));
+
+		datum = heap_getattr(tup, Anum_owner,
+							 RelationGetDescr(catalog), &isnull);
+		Assert(!isnull);
+		ownerId = DatumGetObjectId(datum);
+
+		/* must be owner */
+		if (!has_privs_of_role(GetUserId(), ownerId))
+		{
+			char	   *objname;
+			char		namebuf[NAMEDATALEN];
+
+			if (Anum_name != InvalidAttrNumber)
+			{
+				datum = heap_getattr(tup, Anum_name,
+									 RelationGetDescr(catalog), &isnull);
+				Assert(!isnull);
+				objname = NameStr(*DatumGetName(datum));
+			}
+			else
+			{
+				snprintf(namebuf, sizeof(namebuf), "%u",
+						 HeapTupleGetOid(tup));
+				objname = namebuf;
+			}
+			aclcheck_error(ACLCHECK_NOT_OWNER, aclkind, objname);
+		}
+		heap_close(catalog, RowExclusiveLock);
+	}
+
+	OperatorUpd(address.classId,
+				address.objectId,
+				stmt->defnames);
+
+	return address;
+}
\ No newline at end of file
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4c363d3..09cbf88 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3212,6 +3212,19 @@ _copyAlterOwnerStmt(const AlterOwnerStmt *from)
 	return newnode;
 }
 
+static AlterOperatorStmt *
+_copyAlterOperatorStmt(const AlterOperatorStmt *from)
+{
+	AlterOperatorStmt *newnode = makeNode(AlterOperatorStmt);
+
+	COPY_NODE_FIELD(relation);
+	COPY_NODE_FIELD(object);
+	COPY_NODE_FIELD(objarg);
+	COPY_NODE_FIELD(defnames);
+
+	return newnode;
+}
+
 static RuleStmt *
 _copyRuleStmt(const RuleStmt *from)
 {
@@ -4615,6 +4628,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 f19251e..017891a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1339,6 +1339,17 @@ _equalAlterOwnerStmt(const AlterOwnerStmt *a, const AlterOwnerStmt *b)
 }
 
 static bool
+_equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b)
+{
+	COMPARE_NODE_FIELD(relation);
+	COMPARE_NODE_FIELD(object);
+	COMPARE_NODE_FIELD(objarg);
+	COMPARE_NODE_FIELD(defnames);
+
+	return true;
+}
+
+static bool
 _equalRuleStmt(const RuleStmt *a, const RuleStmt *b)
 {
 	COMPARE_NODE_FIELD(relation);
@@ -2980,6 +2991,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 e0ff6f1..d621b8d 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
@@ -769,6 +769,7 @@ stmt :
 			| AlterGroupStmt
 			| AlterObjectSchemaStmt
 			| AlterOwnerStmt
+			| AlterOperatorStmt
 			| AlterPolicyStmt
 			| AlterSeqStmt
 			| AlterSystemStmt
@@ -8198,6 +8199,24 @@ AlterObjectSchemaStmt:
 
 /*****************************************************************************
  *
+ * ALTER OPERATOR name SET define
+ *
+ *****************************************************************************/
+
+AlterOperatorStmt:
+			ALTER OPERATOR any_operator oper_argtypes SET definition
+				{
+					AlterOperatorStmt *n = makeNode(AlterOperatorStmt);
+					n->object = $3;
+					n->objarg = $4;
+					n->defnames = $6;
+					$$ = (Node *)n;
+				}
+		;
+
+
+/*****************************************************************************
+ *
  * ALTER THING name OWNER TO newname
  *
  *****************************************************************************/
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 7db9f96..5597901 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:
@@ -861,6 +862,19 @@ standard_ProcessUtility(Node *parsetree,
 			}
 			break;
 
+		case T_AlterOperatorStmt:
+			{
+				AlterOperatorStmt *stmt = (AlterOperatorStmt *) parsetree;
+				if (EventTriggerSupportsObjectType(OBJECT_OPERATOR)) {
+					ProcessUtilitySlow(parsetree, queryString,
+									   context, params,
+									   dest, completionTag);
+				}
+				else
+					ExecAlterOperatorStmt(stmt);
+			}
+			break;
+
 		case T_CommentStmt:
 			{
 				CommentStmt *stmt = (CommentStmt *) parsetree;
@@ -916,12 +930,14 @@ ProcessUtilitySlow(Node *parsetree,
 	ObjectAddress address;
 	ObjectAddress secondaryObject = InvalidObjectAddress;
 
+
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
 
 	/* PG_TRY block is to ensure we call EventTriggerEndCompleteQuery */
 	PG_TRY();
 	{
+
 		if (isCompleteQuery)
 			EventTriggerDDLCommandStart(parsetree);
 
@@ -1481,6 +1497,10 @@ ProcessUtilitySlow(Node *parsetree,
 				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_AlterOperatorStmt:
+				address = ExecAlterOperatorStmt((AlterOperatorStmt *) parsetree);
+				break;
+
 			case T_CommentStmt:
 				address = CommentObject((CommentStmt *) parsetree);
 				break;
@@ -2494,6 +2514,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/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 26c9d4e..67420b1 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1837,4 +1837,8 @@ extern ObjectAddress OperatorCreate(const char *operatorName,
 			   bool canMerge,
 			   bool canHash);
 
+extern void OperatorUpd(Oid classId,
+		Oid baseId,
+		List *operator_params);
+
 #endif   /* PG_OPERATOR_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dcb6c08..8ab2207 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 ExecAlterOperatorStmt(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 290cdb3..f8acda4 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 868905b..c20868f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2543,6 +2543,20 @@ typedef struct AlterOwnerStmt
 
 
 /* ----------------------
+ *		Alter Operator Set Restrict, Join
+ * ----------------------
+ */
+typedef struct AlterOperatorStmt
+{
+	NodeTag		type;
+	RangeVar   *relation;		/* in case it's a table */
+	List	   *object;			/* in case it's some other object */
+	List	   *objarg;			/* argument types, if applicable */
+	List	   *defnames;		/* operator */
+} 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 0000000..1dbb05e
--- /dev/null
+++ b/src/test/regress/expected/alter_operator.out
@@ -0,0 +1,78 @@
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+    SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = fn_op2,
+    COMMUTATOR = ===,
+    NEGATOR = !==,
+    RESTRICT = contsel,
+    JOIN = contjoinsel,
+    SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
+);
+--
+-- Reset and set params
+--
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ 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 = 16 AND oprright = 16;
+ oprrest |   oprjoin   
+---------+-------------
+ contsel | contjoinsel
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL,
+										   JOIN = NULL);
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprrest | oprjoin 
+---------+---------
+ -       | -
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel,
+										   JOIN = contjoinsel);
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprrest |   oprjoin   
+---------+-------------
+ contsel | contjoinsel
+(1 row)
+
+--
+-- Trying set the wrong parameters
+--
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+WARNING:  operator attribute "commutator" not recognized
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+WARNING:  operator attribute "negator" not recognized
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = blabla);
+ERROR:  function blabla(internal, oid, internal, integer) does not exist
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = blabla);
+ERROR:  function blabla(internal, oid, internal, smallint, internal) does not exist
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
+WARNING:  operator attribute "commutator" not recognized
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+WARNING:  operator attribute "negator" not recognized
+--
+-- Trying set params from wrong user
+--
+CREATE USER regression_user;
+SET SESSION AUTHORIZATION regression_user;
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ERROR:  must be owner of operator ===
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+ERROR:  must be owner of operator ===
+RESET SESSION AUTHORIZATION;
+DROP USER regression_user;
+DROP OPERATOR === (boolean, boolean);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 91780cd..836f9f9 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml alter_operator
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index a2e0ceb..fbec844 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -146,6 +146,7 @@ test: without_oid
 test: conversion
 test: truncate
 test: alter_table
+test: alter_operator
 test: sequence
 test: polymorphism
 test: rowtypes
diff --git a/src/test/regress/sql/alter_operator.sql b/src/test/regress/sql/alter_operator.sql
new file mode 100644
index 0000000..8365331
--- /dev/null
+++ b/src/test/regress/sql/alter_operator.sql
@@ -0,0 +1,68 @@
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+    SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = fn_op2,
+    COMMUTATOR = ===,
+    NEGATOR = !==,
+    RESTRICT = contsel,
+    JOIN = contjoinsel,
+    SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
+);
+
+
+--
+-- Reset and set params
+--
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL,
+										   JOIN = NULL);
+
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel,
+										   JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+--
+-- Trying set the wrong parameters
+--
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = blabla);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = blabla);
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+
+
+--
+-- Trying set params from wrong user
+--
+
+CREATE USER regression_user;
+SET SESSION AUTHORIZATION regression_user;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+
+RESET SESSION AUTHORIZATION;
+DROP USER regression_user;
+
+DROP OPERATOR === (boolean, boolean);
\ No newline at end of file
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 4b650d1..d86072f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -70,6 +70,7 @@ AlterFunctionStmt
 AlterObjectSchemaStmt
 AlterOpFamilyStmt
 AlterOwnerStmt
+AlterOperatorStmt
 AlterPolicyStmt
 AlterRoleSetStmt
 AlterRoleStmt
#17Robert Haas
robertmhaas@gmail.com
In reply to: Uriy Zhuravlev (#16)
Re: WIP: Enhanced ALTER OPERATOR

On Wed, Jun 24, 2015 at 7:30 AM, Uriy Zhuravlev
<u.zhuravlev@postgrespro.ru> wrote:

Because change the commutator and negator raised questions I suggest 3 version
of the patch without them. In addition, for us now is much more important
restrict and join (for "Selectivity estimation for intarray" patch).

Seems broadly sensible. You will need to write docs.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Uriy Zhuravlev
u.zhuravlev@postgrespro.ru
In reply to: Uriy Zhuravlev (#1)
1 attachment(s)
Re: WIP: Enhanced ALTER OPERATOR

Hello hackers.

This is the fifth version of the patch (the fourth was unsuccessful :)).
I added documentation and was held a small refactoring.

Thanks.
--
Uriy Zhuravlev
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

extend_alter_operator_v5.patchtext/x-patch; charset=UTF-8; name=extend_alter_operator_v5.patchDownload
diff --git a/doc/src/sgml/ref/alter_operator.sgml b/doc/src/sgml/ref/alter_operator.sgml
index bdb2d02..4aaeed0 100644
--- a/doc/src/sgml/ref/alter_operator.sgml
+++ b/doc/src/sgml/ref/alter_operator.sgml
@@ -26,6 +26,11 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
 
 ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</replaceable> | NONE } , { <replaceable>right_type</replaceable> | NONE } )
                SET SCHEMA <replaceable>new_schema</replaceable>
+
+ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</replaceable> | NONE } , { <replaceable>right_type</replaceable> | NONE } ) SET (
+    [, RESTRICT = { <replaceable class="parameter">res_proc</replaceable> | NULL } ]
+    [, JOIN = { <replaceable class="parameter">join_proc</replaceable> | NULL } ]
+)
 </synopsis>
  </refsynopsisdiv>
 
@@ -34,8 +39,7 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
 
   <para>
    <command>ALTER OPERATOR</command> changes the definition of
-   an operator.  The only currently available functionality is to change the
-   owner of the operator.
+   an operator.
   </para>
 
   <para>
@@ -98,6 +102,25 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+     <term><replaceable class="parameter">res_proc</replaceable></term>
+     <listitem>
+       <para>
+         The restriction selectivity estimator function for this operator.
+       </para>
+      </listitem>
+   </varlistentry>
+
+   <varlistentry>
+     <term><replaceable class="parameter">join_proc</replaceable></term>
+     <listitem>
+       <para>
+         The join selectivity estimator function for this operator.
+       </para>
+     </listitem>
+   </varlistentry>
+
   </variablelist>
  </refsect1>
 
@@ -109,6 +132,13 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
 <programlisting>
 ALTER OPERATOR @@ (text, text) OWNER TO joe;
 </programlisting></para>
+
+  <para>
+    Change the restriction and join selectivity estimator function of a custom operator <literal>a && b</literal> for type <type>int[]</type>:
+<programlisting>
+ALTER OPERATOR && (_int4, _int4) SET (RESTRICT = _int_contsel, JOIN = _int_contjoinsel);
+</programlisting></para>
+
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 072f530..f5ff381 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -28,8 +28,10 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/defrem.h"
 #include "miscadmin.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_func.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -53,7 +55,7 @@ static Oid OperatorShellMake(const char *operatorName,
 				  Oid leftTypeId,
 				  Oid rightTypeId);
 
-static void OperatorUpd(Oid baseId, Oid commId, Oid negId);
+static void ShellOperatorUpd(Oid baseId, Oid commId, Oid negId);
 
 static Oid get_other_operator(List *otherOp,
 				   Oid otherLeftTypeId, Oid otherRightTypeId,
@@ -563,7 +565,7 @@ OperatorCreate(const char *operatorName,
 		commutatorId = operatorObjectId;
 
 	if (OidIsValid(commutatorId) || OidIsValid(negatorId))
-		OperatorUpd(operatorObjectId, commutatorId, negatorId);
+		ShellOperatorUpd(operatorObjectId, commutatorId, negatorId);
 
 	return address;
 }
@@ -633,7 +635,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
 }
 
 /*
- * OperatorUpd
+ * ShellOperatorUpd
  *
  *	For a given operator, look up its negator and commutator operators.
  *	If they are defined, but their negator and commutator fields
@@ -642,7 +644,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
  *	which are the negator or commutator of each other.
  */
 static void
-OperatorUpd(Oid baseId, Oid commId, Oid negId)
+ShellOperatorUpd(Oid baseId, Oid commId, Oid negId)
 {
 	int			i;
 	Relation	pg_operator_desc;
@@ -864,3 +866,150 @@ makeOperatorDependencies(HeapTuple tuple)
 
 	return myself;
 }
+
+/*
+ * Operator update aka ALTER OPERATOR for RESTRICT, JOIN
+ */
+void OperatorUpd(Oid classId,
+				 Oid baseId,
+				 List *operator_params)
+{
+	int			i;
+	ListCell	*pl;
+	Relation	catalog;
+	HeapTuple	tup;
+	Oid 		operator_param_id = 0;
+	bool		nulls[Natts_pg_operator];
+	bool		replaces[Natts_pg_operator];
+	Datum		values[Natts_pg_operator];
+
+	for (i = 0; i < Natts_pg_operator; ++i)
+	{
+		values[i] = (Datum) 0;
+		replaces[i] = false;
+		nulls[i] = false;
+	}
+
+	catalog = heap_open(classId, RowExclusiveLock);
+	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(baseId));
+	if (HeapTupleIsValid(tup))
+	{
+		/*
+		 * loop over the definition list and extract the information we need.
+		 */
+		foreach(pl, operator_params)
+		{
+			DefElem    *defel = (DefElem *) lfirst(pl);
+			List	   *param = defGetQualifiedName(defel);
+			int			param_type;
+
+			if (pg_strcasecmp(defel->defname, "restrict") == 0)
+				param_type = Anum_pg_operator_oprrest;
+			else if (pg_strcasecmp(defel->defname, "join") == 0)
+				param_type = Anum_pg_operator_oprjoin;
+			else
+			{
+				ereport(WARNING,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("operator attribute \"%s\" not recognized",
+								defel->defname)));
+				continue;
+			}
+
+			/* Resets if written NULL */
+			if (PointerIsValid(param) && pg_strcasecmp(NameListToString(param), "null") == 0)
+			{
+				values[param_type - 1] = ObjectIdGetDatum(InvalidOid);
+				replaces[param_type - 1] = true;
+				continue;
+			}
+
+			if (!PointerIsValid(param))
+				operator_param_id = InvalidOid;
+			else if (param_type == Anum_pg_operator_oprrest)
+			{
+				Oid			typeId[5];
+				AclResult	aclresult;
+				typeId[0] = INTERNALOID;	/* PlannerInfo */
+				typeId[1] = OIDOID;		/* operator OID */
+				typeId[2] = INTERNALOID;	/* args list */
+				typeId[3] = INT4OID;	/* varRelid */
+
+				operator_param_id = LookupFuncName(param, 4, typeId, false);
+
+				/* estimators must return float8 */
+				if (get_func_rettype(operator_param_id) != FLOAT8OID)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("restriction estimator function %s must return type \"float8\"",
+									NameListToString(param))));
+
+				/* Require EXECUTE rights for the estimator */
+				aclresult = pg_proc_aclcheck(operator_param_id, GetUserId(), ACL_EXECUTE);
+				if (aclresult != ACLCHECK_OK)
+					aclcheck_error(aclresult, ACL_KIND_PROC,
+								   NameListToString(param));
+			}
+			else if (param_type == Anum_pg_operator_oprjoin)
+			{
+				Oid			typeId[5];
+				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.
+				 */
+				operator_param_id = LookupFuncName(param, 5, typeId, true);
+				if (!OidIsValid(operator_param_id))
+					operator_param_id = LookupFuncName(param, 4, typeId, true);
+				/* If not found, reference the 5-argument signature in error msg */
+				if (!OidIsValid(operator_param_id))
+					operator_param_id = LookupFuncName(param, 5, typeId, false);
+
+				/* estimators must return float8 */
+				if (get_func_rettype(operator_param_id) != FLOAT8OID)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("restriction estimator function %s must return type \"float8\"",
+									NameListToString(param))));
+
+				/* Require EXECUTE rights for the estimator */
+				aclresult = pg_proc_aclcheck(operator_param_id, GetUserId(), ACL_EXECUTE);
+				if (aclresult != ACLCHECK_OK)
+					aclcheck_error(aclresult, ACL_KIND_PROC,
+								   NameListToString(param));
+			}
+
+			if (OidIsValid(operator_param_id))
+			{
+				values[param_type - 1] = ObjectIdGetDatum(operator_param_id);
+				replaces[param_type - 1] = true;
+			}
+			else
+				ereport(ERROR,
+					(errmsg_internal("Not found function or operator for alter operator")));
+		}
+
+		/* Update heap */
+		tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(baseId));
+		if (HeapTupleIsValid(tup))
+		{
+			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);
+}
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index b4a1aac..181823b 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -320,3 +320,74 @@ RemoveOperatorById(Oid operOid)
 
 	heap_close(relation, RowExclusiveLock);
 }
+
+ObjectAddress
+ExecAlterOperatorStmt(AlterOperatorStmt *stmt)
+{
+	ObjectAddress address;
+	Relation	catalog;
+	Relation	relation;
+	Datum		datum;
+	Oid			ownerId;
+	bool		isnull;
+	HeapTuple	tup;
+
+	/* Address to be modified operator. */
+	address = get_object_address(OBJECT_OPERATOR,
+								 stmt->object,
+								 stmt->objarg,
+								 &relation,
+								 AccessExclusiveLock,
+								 false);
+	Assert(relation == NULL);
+
+	/* Check user rights. */
+	if (!superuser())
+	{
+		AclObjectKind 	aclkind = get_object_aclkind(address.classId);
+		AttrNumber		Anum_name = get_object_attnum_name(address.classId);
+		AttrNumber		Anum_owner = get_object_attnum_owner(address.classId);;
+
+		catalog = heap_open(address.classId, RowExclusiveLock);
+
+		tup = get_catalog_object_by_oid(catalog, address.objectId);
+
+		if (tup == NULL)
+			elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+				 address.objectId, RelationGetRelationName(catalog));
+
+		datum = heap_getattr(tup, Anum_owner,
+							 RelationGetDescr(catalog), &isnull);
+		Assert(!isnull);
+		ownerId = DatumGetObjectId(datum);
+
+		/* must be owner */
+		if (!has_privs_of_role(GetUserId(), ownerId))
+		{
+			char	   *objname;
+			char		namebuf[NAMEDATALEN];
+
+			if (Anum_name != InvalidAttrNumber)
+			{
+				datum = heap_getattr(tup, Anum_name,
+									 RelationGetDescr(catalog), &isnull);
+				Assert(!isnull);
+				objname = NameStr(*DatumGetName(datum));
+			}
+			else
+			{
+				snprintf(namebuf, sizeof(namebuf), "%u",
+						 HeapTupleGetOid(tup));
+				objname = namebuf;
+			}
+			aclcheck_error(ACLCHECK_NOT_OWNER, aclkind, objname);
+		}
+		heap_close(catalog, RowExclusiveLock);
+	}
+
+	OperatorUpd(address.classId,
+				address.objectId,
+				stmt->defnames);
+
+	return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4c363d3..09cbf88 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3212,6 +3212,19 @@ _copyAlterOwnerStmt(const AlterOwnerStmt *from)
 	return newnode;
 }
 
+static AlterOperatorStmt *
+_copyAlterOperatorStmt(const AlterOperatorStmt *from)
+{
+	AlterOperatorStmt *newnode = makeNode(AlterOperatorStmt);
+
+	COPY_NODE_FIELD(relation);
+	COPY_NODE_FIELD(object);
+	COPY_NODE_FIELD(objarg);
+	COPY_NODE_FIELD(defnames);
+
+	return newnode;
+}
+
 static RuleStmt *
 _copyRuleStmt(const RuleStmt *from)
 {
@@ -4615,6 +4628,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 f19251e..017891a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1339,6 +1339,17 @@ _equalAlterOwnerStmt(const AlterOwnerStmt *a, const AlterOwnerStmt *b)
 }
 
 static bool
+_equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b)
+{
+	COMPARE_NODE_FIELD(relation);
+	COMPARE_NODE_FIELD(object);
+	COMPARE_NODE_FIELD(objarg);
+	COMPARE_NODE_FIELD(defnames);
+
+	return true;
+}
+
+static bool
 _equalRuleStmt(const RuleStmt *a, const RuleStmt *b)
 {
 	COMPARE_NODE_FIELD(relation);
@@ -2980,6 +2991,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 e0ff6f1..d621b8d 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
@@ -769,6 +769,7 @@ stmt :
 			| AlterGroupStmt
 			| AlterObjectSchemaStmt
 			| AlterOwnerStmt
+			| AlterOperatorStmt
 			| AlterPolicyStmt
 			| AlterSeqStmt
 			| AlterSystemStmt
@@ -8198,6 +8199,24 @@ AlterObjectSchemaStmt:
 
 /*****************************************************************************
  *
+ * ALTER OPERATOR name SET define
+ *
+ *****************************************************************************/
+
+AlterOperatorStmt:
+			ALTER OPERATOR any_operator oper_argtypes SET definition
+				{
+					AlterOperatorStmt *n = makeNode(AlterOperatorStmt);
+					n->object = $3;
+					n->objarg = $4;
+					n->defnames = $6;
+					$$ = (Node *)n;
+				}
+		;
+
+
+/*****************************************************************************
+ *
  * ALTER THING name OWNER TO newname
  *
  *****************************************************************************/
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 7db9f96..213f2ce 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:
@@ -861,6 +862,19 @@ standard_ProcessUtility(Node *parsetree,
 			}
 			break;
 
+		case T_AlterOperatorStmt:
+			{
+				AlterOperatorStmt *stmt = (AlterOperatorStmt *) parsetree;
+				if (EventTriggerSupportsObjectType(OBJECT_OPERATOR)) {
+					ProcessUtilitySlow(parsetree, queryString,
+									   context, params,
+									   dest, completionTag);
+				}
+				else
+					ExecAlterOperatorStmt(stmt);
+			}
+			break;
+
 		case T_CommentStmt:
 			{
 				CommentStmt *stmt = (CommentStmt *) parsetree;
@@ -1481,6 +1495,10 @@ ProcessUtilitySlow(Node *parsetree,
 				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_AlterOperatorStmt:
+				address = ExecAlterOperatorStmt((AlterOperatorStmt *) parsetree);
+				break;
+
 			case T_CommentStmt:
 				address = CommentObject((CommentStmt *) parsetree);
 				break;
@@ -2494,6 +2512,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/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 26c9d4e..67420b1 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1837,4 +1837,8 @@ extern ObjectAddress OperatorCreate(const char *operatorName,
 			   bool canMerge,
 			   bool canHash);
 
+extern void OperatorUpd(Oid classId,
+		Oid baseId,
+		List *operator_params);
+
 #endif   /* PG_OPERATOR_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dcb6c08..8ab2207 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 ExecAlterOperatorStmt(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 290cdb3..f8acda4 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 868905b..c20868f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2543,6 +2543,20 @@ typedef struct AlterOwnerStmt
 
 
 /* ----------------------
+ *		Alter Operator Set Restrict, Join
+ * ----------------------
+ */
+typedef struct AlterOperatorStmt
+{
+	NodeTag		type;
+	RangeVar   *relation;		/* in case it's a table */
+	List	   *object;			/* in case it's some other object */
+	List	   *objarg;			/* argument types, if applicable */
+	List	   *defnames;		/* operator */
+} 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 0000000..1dbb05e
--- /dev/null
+++ b/src/test/regress/expected/alter_operator.out
@@ -0,0 +1,78 @@
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+    SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = fn_op2,
+    COMMUTATOR = ===,
+    NEGATOR = !==,
+    RESTRICT = contsel,
+    JOIN = contjoinsel,
+    SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
+);
+--
+-- Reset and set params
+--
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ 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 = 16 AND oprright = 16;
+ oprrest |   oprjoin   
+---------+-------------
+ contsel | contjoinsel
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL,
+										   JOIN = NULL);
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprrest | oprjoin 
+---------+---------
+ -       | -
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel,
+										   JOIN = contjoinsel);
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprrest |   oprjoin   
+---------+-------------
+ contsel | contjoinsel
+(1 row)
+
+--
+-- Trying set the wrong parameters
+--
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+WARNING:  operator attribute "commutator" not recognized
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+WARNING:  operator attribute "negator" not recognized
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = blabla);
+ERROR:  function blabla(internal, oid, internal, integer) does not exist
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = blabla);
+ERROR:  function blabla(internal, oid, internal, smallint, internal) does not exist
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
+WARNING:  operator attribute "commutator" not recognized
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+WARNING:  operator attribute "negator" not recognized
+--
+-- Trying set params from wrong user
+--
+CREATE USER regression_user;
+SET SESSION AUTHORIZATION regression_user;
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ERROR:  must be owner of operator ===
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+ERROR:  must be owner of operator ===
+RESET SESSION AUTHORIZATION;
+DROP USER regression_user;
+DROP OPERATOR === (boolean, boolean);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 91780cd..836f9f9 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml alter_operator
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index a2e0ceb..fbec844 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -146,6 +146,7 @@ test: without_oid
 test: conversion
 test: truncate
 test: alter_table
+test: alter_operator
 test: sequence
 test: polymorphism
 test: rowtypes
diff --git a/src/test/regress/sql/alter_operator.sql b/src/test/regress/sql/alter_operator.sql
new file mode 100644
index 0000000..967de1f
--- /dev/null
+++ b/src/test/regress/sql/alter_operator.sql
@@ -0,0 +1,68 @@
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+    SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = fn_op2,
+    COMMUTATOR = ===,
+    NEGATOR = !==,
+    RESTRICT = contsel,
+    JOIN = contjoinsel,
+    SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
+);
+
+
+--
+-- Reset and set params
+--
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL,
+										   JOIN = NULL);
+
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel,
+										   JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+--
+-- Trying set the wrong parameters
+--
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = blabla);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = blabla);
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+
+
+--
+-- Trying set params from wrong user
+--
+
+CREATE USER regression_user;
+SET SESSION AUTHORIZATION regression_user;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+
+RESET SESSION AUTHORIZATION;
+DROP USER regression_user;
+
+DROP OPERATOR === (boolean, boolean);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 4b650d1..d86072f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -70,6 +70,7 @@ AlterFunctionStmt
 AlterObjectSchemaStmt
 AlterOpFamilyStmt
 AlterOwnerStmt
+AlterOperatorStmt
 AlterPolicyStmt
 AlterRoleSetStmt
 AlterRoleStmt
#19Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Uriy Zhuravlev (#18)
1 attachment(s)
Re: WIP: Enhanced ALTER OPERATOR

On 07/06/2015 07:21 PM, Uriy Zhuravlev wrote:

Hello hackers.

This is the fifth version of the patch (the fourth was unsuccessful :)).
I added documentation and was held a small refactoring.

I edited the formatting of the syntax page a bit, and came up with this:

ALTER OPERATOR name ( { left_type | NONE } , { right_type | NONE } )
SET ( { RESTRICT = { res_proc | NULL }
| JOIN = { join_proc | NULL }
} [, ... ] )

A couple of minor issues with the syntax itself:

* I think it'd be better to use NONE instead of NULL above, to remove
the estimator. It seems inconsistent when we've used NONE to mean
"missing" earlier in the same statement.

* The way you check for the NULL in OperatorUpd is wrong. It cannot
distinguish between a quoted "null", meaning a function called "null",
and a NULL, meaning no function. (You can argue that you're just asking
for trouble if you name any function "null", but we're careful to deal
with that correctly everywhere else.) You don't have the information
about quoting once you leave the parser, so you'll have to distinguish
those in the grammar.

Attached is a new version of your patch, rebased over current master,
and the docs formatting fixes.

- Heikki

Attachments:

extend_alter_operator_v5-heikki.patchtext/x-diff; name=extend_alter_operator_v5-heikki.patchDownload
diff --git a/doc/src/sgml/ref/alter_operator.sgml b/doc/src/sgml/ref/alter_operator.sgml
index 8a7af50..b8c353f 100644
--- a/doc/src/sgml/ref/alter_operator.sgml
+++ b/doc/src/sgml/ref/alter_operator.sgml
@@ -26,6 +26,11 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
 
 ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</replaceable> | NONE } , { <replaceable>right_type</replaceable> | NONE } )
     SET SCHEMA <replaceable>new_schema</replaceable>
+
+ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</replaceable> | NONE } , { <replaceable>right_type</replaceable> | NONE } )
+    SET ( {  RESTRICT = { <replaceable class="parameter">res_proc</replaceable> | NONE }
+           | JOIN = { <replaceable class="parameter">join_proc</replaceable> | NONE }
+         } [, ... ] )
 </synopsis>
  </refsynopsisdiv>
 
@@ -34,8 +39,7 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
 
   <para>
    <command>ALTER OPERATOR</command> changes the definition of
-   an operator.  The only currently available functionality is to change the
-   owner of the operator.
+   an operator.
   </para>
 
   <para>
@@ -98,6 +102,25 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+     <term><replaceable class="parameter">res_proc</replaceable></term>
+     <listitem>
+       <para>
+         The restriction selectivity estimator function for this operator; write NONE to remove existing selectivity estimator.
+       </para>
+      </listitem>
+   </varlistentry>
+
+   <varlistentry>
+     <term><replaceable class="parameter">join_proc</replaceable></term>
+     <listitem>
+       <para>
+         The join selectivity estimator function for this operator; write NONE to remove existing selectivity estimator.
+       </para>
+     </listitem>
+   </varlistentry>
+
   </variablelist>
  </refsect1>
 
@@ -109,6 +132,13 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
 <programlisting>
 ALTER OPERATOR @@ (text, text) OWNER TO joe;
 </programlisting></para>
+
+  <para>
+    Change the restriction and join selectivity estimator function of a custom operator <literal>a && b</literal> for type <type>int[]</type>:
+<programlisting>
+ALTER OPERATOR && (_int4, _int4) SET (RESTRICT = _int_contsel, JOIN = _int_contjoinsel);
+</programlisting></para>
+
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 072f530..b981a44 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -28,8 +28,10 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/defrem.h"
 #include "miscadmin.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_func.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -53,7 +55,7 @@ static Oid OperatorShellMake(const char *operatorName,
 				  Oid leftTypeId,
 				  Oid rightTypeId);
 
-static void OperatorUpd(Oid baseId, Oid commId, Oid negId);
+static void ShellOperatorUpd(Oid baseId, Oid commId, Oid negId);
 
 static Oid get_other_operator(List *otherOp,
 				   Oid otherLeftTypeId, Oid otherRightTypeId,
@@ -563,7 +565,7 @@ OperatorCreate(const char *operatorName,
 		commutatorId = operatorObjectId;
 
 	if (OidIsValid(commutatorId) || OidIsValid(negatorId))
-		OperatorUpd(operatorObjectId, commutatorId, negatorId);
+		ShellOperatorUpd(operatorObjectId, commutatorId, negatorId);
 
 	return address;
 }
@@ -633,7 +635,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
 }
 
 /*
- * OperatorUpd
+ * ShellOperatorUpd
  *
  *	For a given operator, look up its negator and commutator operators.
  *	If they are defined, but their negator and commutator fields
@@ -642,7 +644,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
  *	which are the negator or commutator of each other.
  */
 static void
-OperatorUpd(Oid baseId, Oid commId, Oid negId)
+ShellOperatorUpd(Oid baseId, Oid commId, Oid negId)
 {
 	int			i;
 	Relation	pg_operator_desc;
@@ -864,3 +866,149 @@ makeOperatorDependencies(HeapTuple tuple)
 
 	return myself;
 }
+
+/*
+ * Operator update aka ALTER OPERATOR for RESTRICT, JOIN
+ */
+void
+OperatorUpd(Oid classId, Oid baseId, List *operator_params)
+{
+	int			i;
+	ListCell	*pl;
+	Relation	catalog;
+	HeapTuple	tup;
+	Oid 		operator_param_id = 0;
+	bool		nulls[Natts_pg_operator];
+	bool		replaces[Natts_pg_operator];
+	Datum		values[Natts_pg_operator];
+
+	for (i = 0; i < Natts_pg_operator; ++i)
+	{
+		values[i] = (Datum) 0;
+		replaces[i] = false;
+		nulls[i] = false;
+	}
+
+	catalog = heap_open(classId, RowExclusiveLock);
+	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(baseId));
+	if (HeapTupleIsValid(tup))
+	{
+		/*
+		 * loop over the definition list and extract the information we need.
+		 */
+		foreach(pl, operator_params)
+		{
+			DefElem    *defel = (DefElem *) lfirst(pl);
+			List	   *param = defGetQualifiedName(defel);
+			int			param_type;
+
+			if (pg_strcasecmp(defel->defname, "restrict") == 0)
+				param_type = Anum_pg_operator_oprrest;
+			else if (pg_strcasecmp(defel->defname, "join") == 0)
+				param_type = Anum_pg_operator_oprjoin;
+			else
+			{
+				ereport(WARNING,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("operator attribute \"%s\" not recognized",
+								defel->defname)));
+				continue;
+			}
+
+			/* Resets if written NULL */
+			if (PointerIsValid(param) && pg_strcasecmp(NameListToString(param), "null") == 0)
+			{
+				values[param_type - 1] = ObjectIdGetDatum(InvalidOid);
+				replaces[param_type - 1] = true;
+				continue;
+			}
+
+			if (!PointerIsValid(param))
+				operator_param_id = InvalidOid;
+			else if (param_type == Anum_pg_operator_oprrest)
+			{
+				Oid			typeId[5];
+				AclResult	aclresult;
+				typeId[0] = INTERNALOID;	/* PlannerInfo */
+				typeId[1] = OIDOID;		/* operator OID */
+				typeId[2] = INTERNALOID;	/* args list */
+				typeId[3] = INT4OID;	/* varRelid */
+
+				operator_param_id = LookupFuncName(param, 4, typeId, false);
+
+				/* estimators must return float8 */
+				if (get_func_rettype(operator_param_id) != FLOAT8OID)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("restriction estimator function %s must return type \"float8\"",
+									NameListToString(param))));
+
+				/* Require EXECUTE rights for the estimator */
+				aclresult = pg_proc_aclcheck(operator_param_id, GetUserId(), ACL_EXECUTE);
+				if (aclresult != ACLCHECK_OK)
+					aclcheck_error(aclresult, ACL_KIND_PROC,
+								   NameListToString(param));
+			}
+			else if (param_type == Anum_pg_operator_oprjoin)
+			{
+				Oid			typeId[5];
+				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.
+				 */
+				operator_param_id = LookupFuncName(param, 5, typeId, true);
+				if (!OidIsValid(operator_param_id))
+					operator_param_id = LookupFuncName(param, 4, typeId, true);
+				/* If not found, reference the 5-argument signature in error msg */
+				if (!OidIsValid(operator_param_id))
+					operator_param_id = LookupFuncName(param, 5, typeId, false);
+
+				/* estimators must return float8 */
+				if (get_func_rettype(operator_param_id) != FLOAT8OID)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("restriction estimator function %s must return type \"float8\"",
+									NameListToString(param))));
+
+				/* Require EXECUTE rights for the estimator */
+				aclresult = pg_proc_aclcheck(operator_param_id, GetUserId(), ACL_EXECUTE);
+				if (aclresult != ACLCHECK_OK)
+					aclcheck_error(aclresult, ACL_KIND_PROC,
+								   NameListToString(param));
+			}
+
+			if (OidIsValid(operator_param_id))
+			{
+				values[param_type - 1] = ObjectIdGetDatum(operator_param_id);
+				replaces[param_type - 1] = true;
+			}
+			else
+				ereport(ERROR,
+					(errmsg_internal("Not found function or operator for alter operator")));
+		}
+
+		/* Update heap */
+		tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(baseId));
+		if (HeapTupleIsValid(tup))
+		{
+			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);
+}
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index b4a1aac..181823b 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -320,3 +320,74 @@ RemoveOperatorById(Oid operOid)
 
 	heap_close(relation, RowExclusiveLock);
 }
+
+ObjectAddress
+ExecAlterOperatorStmt(AlterOperatorStmt *stmt)
+{
+	ObjectAddress address;
+	Relation	catalog;
+	Relation	relation;
+	Datum		datum;
+	Oid			ownerId;
+	bool		isnull;
+	HeapTuple	tup;
+
+	/* Address to be modified operator. */
+	address = get_object_address(OBJECT_OPERATOR,
+								 stmt->object,
+								 stmt->objarg,
+								 &relation,
+								 AccessExclusiveLock,
+								 false);
+	Assert(relation == NULL);
+
+	/* Check user rights. */
+	if (!superuser())
+	{
+		AclObjectKind 	aclkind = get_object_aclkind(address.classId);
+		AttrNumber		Anum_name = get_object_attnum_name(address.classId);
+		AttrNumber		Anum_owner = get_object_attnum_owner(address.classId);;
+
+		catalog = heap_open(address.classId, RowExclusiveLock);
+
+		tup = get_catalog_object_by_oid(catalog, address.objectId);
+
+		if (tup == NULL)
+			elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+				 address.objectId, RelationGetRelationName(catalog));
+
+		datum = heap_getattr(tup, Anum_owner,
+							 RelationGetDescr(catalog), &isnull);
+		Assert(!isnull);
+		ownerId = DatumGetObjectId(datum);
+
+		/* must be owner */
+		if (!has_privs_of_role(GetUserId(), ownerId))
+		{
+			char	   *objname;
+			char		namebuf[NAMEDATALEN];
+
+			if (Anum_name != InvalidAttrNumber)
+			{
+				datum = heap_getattr(tup, Anum_name,
+									 RelationGetDescr(catalog), &isnull);
+				Assert(!isnull);
+				objname = NameStr(*DatumGetName(datum));
+			}
+			else
+			{
+				snprintf(namebuf, sizeof(namebuf), "%u",
+						 HeapTupleGetOid(tup));
+				objname = namebuf;
+			}
+			aclcheck_error(ACLCHECK_NOT_OWNER, aclkind, objname);
+		}
+		heap_close(catalog, RowExclusiveLock);
+	}
+
+	OperatorUpd(address.classId,
+				address.objectId,
+				stmt->defnames);
+
+	return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4c363d3..09cbf88 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3212,6 +3212,19 @@ _copyAlterOwnerStmt(const AlterOwnerStmt *from)
 	return newnode;
 }
 
+static AlterOperatorStmt *
+_copyAlterOperatorStmt(const AlterOperatorStmt *from)
+{
+	AlterOperatorStmt *newnode = makeNode(AlterOperatorStmt);
+
+	COPY_NODE_FIELD(relation);
+	COPY_NODE_FIELD(object);
+	COPY_NODE_FIELD(objarg);
+	COPY_NODE_FIELD(defnames);
+
+	return newnode;
+}
+
 static RuleStmt *
 _copyRuleStmt(const RuleStmt *from)
 {
@@ -4615,6 +4628,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 f19251e..017891a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1339,6 +1339,17 @@ _equalAlterOwnerStmt(const AlterOwnerStmt *a, const AlterOwnerStmt *b)
 }
 
 static bool
+_equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b)
+{
+	COMPARE_NODE_FIELD(relation);
+	COMPARE_NODE_FIELD(object);
+	COMPARE_NODE_FIELD(objarg);
+	COMPARE_NODE_FIELD(defnames);
+
+	return true;
+}
+
+static bool
 _equalRuleStmt(const RuleStmt *a, const RuleStmt *b)
 {
 	COMPARE_NODE_FIELD(relation);
@@ -2980,6 +2991,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 e0ff6f1..fdc95e1 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
@@ -769,6 +769,7 @@ stmt :
 			| AlterGroupStmt
 			| AlterObjectSchemaStmt
 			| AlterOwnerStmt
+			| AlterOperatorStmt
 			| AlterPolicyStmt
 			| AlterSeqStmt
 			| AlterSystemStmt
@@ -9113,6 +9114,25 @@ CreateConversionStmt:
 			}
 		;
 
+
+/*****************************************************************************
+ *
+ * ALTER OPERATOR name SET (...)
+ *
+ *****************************************************************************/
+
+AlterOperatorStmt:
+			ALTER OPERATOR any_operator oper_argtypes SET definition
+				{
+					AlterOperatorStmt *n = makeNode(AlterOperatorStmt);
+					n->object = $3;
+					n->objarg = $4;
+					n->defnames = $6;
+					$$ = (Node *)n;
+				}
+		;
+
+
 /*****************************************************************************
  *
  *		QUERY:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 0dabcc1..cf60c7c 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:
@@ -861,6 +862,19 @@ standard_ProcessUtility(Node *parsetree,
 			}
 			break;
 
+		case T_AlterOperatorStmt:
+			{
+				AlterOperatorStmt *stmt = (AlterOperatorStmt *) parsetree;
+				if (EventTriggerSupportsObjectType(OBJECT_OPERATOR)) {
+					ProcessUtilitySlow(parsetree, queryString,
+									   context, params,
+									   dest, completionTag);
+				}
+				else
+					ExecAlterOperatorStmt(stmt);
+			}
+			break;
+
 		case T_CommentStmt:
 			{
 				CommentStmt *stmt = (CommentStmt *) parsetree;
@@ -1481,6 +1495,10 @@ ProcessUtilitySlow(Node *parsetree,
 				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_AlterOperatorStmt:
+				address = ExecAlterOperatorStmt((AlterOperatorStmt *) parsetree);
+				break;
+
 			case T_CommentStmt:
 				address = CommentObject((CommentStmt *) parsetree);
 				break;
@@ -2494,6 +2512,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/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 26c9d4e..67420b1 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1837,4 +1837,8 @@ extern ObjectAddress OperatorCreate(const char *operatorName,
 			   bool canMerge,
 			   bool canHash);
 
+extern void OperatorUpd(Oid classId,
+		Oid baseId,
+		List *operator_params);
+
 #endif   /* PG_OPERATOR_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 9b81c16..e9396d3 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 ExecAlterOperatorStmt(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 290cdb3..f8acda4 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 868905b..c20868f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2543,6 +2543,20 @@ typedef struct AlterOwnerStmt
 
 
 /* ----------------------
+ *		Alter Operator Set Restrict, Join
+ * ----------------------
+ */
+typedef struct AlterOperatorStmt
+{
+	NodeTag		type;
+	RangeVar   *relation;		/* in case it's a table */
+	List	   *object;			/* in case it's some other object */
+	List	   *objarg;			/* argument types, if applicable */
+	List	   *defnames;		/* operator */
+} 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 0000000..1dbb05e
--- /dev/null
+++ b/src/test/regress/expected/alter_operator.out
@@ -0,0 +1,78 @@
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+    SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = fn_op2,
+    COMMUTATOR = ===,
+    NEGATOR = !==,
+    RESTRICT = contsel,
+    JOIN = contjoinsel,
+    SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
+);
+--
+-- Reset and set params
+--
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ 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 = 16 AND oprright = 16;
+ oprrest |   oprjoin   
+---------+-------------
+ contsel | contjoinsel
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL,
+										   JOIN = NULL);
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprrest | oprjoin 
+---------+---------
+ -       | -
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel,
+										   JOIN = contjoinsel);
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprrest |   oprjoin   
+---------+-------------
+ contsel | contjoinsel
+(1 row)
+
+--
+-- Trying set the wrong parameters
+--
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+WARNING:  operator attribute "commutator" not recognized
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+WARNING:  operator attribute "negator" not recognized
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = blabla);
+ERROR:  function blabla(internal, oid, internal, integer) does not exist
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = blabla);
+ERROR:  function blabla(internal, oid, internal, smallint, internal) does not exist
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
+WARNING:  operator attribute "commutator" not recognized
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+WARNING:  operator attribute "negator" not recognized
+--
+-- Trying set params from wrong user
+--
+CREATE USER regression_user;
+SET SESSION AUTHORIZATION regression_user;
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ERROR:  must be owner of operator ===
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+ERROR:  must be owner of operator ===
+RESET SESSION AUTHORIZATION;
+DROP USER regression_user;
+DROP OPERATOR === (boolean, boolean);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 91780cd..836f9f9 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml alter_operator
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index a2e0ceb..fbec844 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -146,6 +146,7 @@ test: without_oid
 test: conversion
 test: truncate
 test: alter_table
+test: alter_operator
 test: sequence
 test: polymorphism
 test: rowtypes
diff --git a/src/test/regress/sql/alter_operator.sql b/src/test/regress/sql/alter_operator.sql
new file mode 100644
index 0000000..967de1f
--- /dev/null
+++ b/src/test/regress/sql/alter_operator.sql
@@ -0,0 +1,68 @@
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+    SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = fn_op2,
+    COMMUTATOR = ===,
+    NEGATOR = !==,
+    RESTRICT = contsel,
+    JOIN = contjoinsel,
+    SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
+);
+
+
+--
+-- Reset and set params
+--
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL,
+										   JOIN = NULL);
+
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel,
+										   JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+--
+-- Trying set the wrong parameters
+--
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = blabla);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = blabla);
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+
+
+--
+-- Trying set params from wrong user
+--
+
+CREATE USER regression_user;
+SET SESSION AUTHORIZATION regression_user;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NULL);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NULL);
+
+RESET SESSION AUTHORIZATION;
+DROP USER regression_user;
+
+DROP OPERATOR === (boolean, boolean);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 4b650d1..d86072f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -70,6 +70,7 @@ AlterFunctionStmt
 AlterObjectSchemaStmt
 AlterOpFamilyStmt
 AlterOwnerStmt
+AlterOperatorStmt
 AlterPolicyStmt
 AlterRoleSetStmt
 AlterRoleStmt
#20Uriy Zhuravlev
u.zhuravlev@postgrespro.ru
In reply to: Heikki Linnakangas (#19)
Re: WIP: Enhanced ALTER OPERATOR

Hello.

On Friday 10 July 2015 15:46:35 you wrote:

* I think it'd be better to use NONE instead of NULL above, to remove
the estimator. It seems inconsistent when we've used NONE to mean
"missing" earlier in the same statement.

Ok, you right.

* The way you check for the NULL in OperatorUpd is wrong. It cannot
distinguish between a quoted "null", meaning a function called "null",
and a NULL, meaning no function.

With "none" has a similar problem. I need to correct the grammar.

Attached is a new version of your patch, rebased over current master,
and the docs formatting fixes.

Thanks. I hope to send the new patch on Monday.

--
Uriy Zhuravlev
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Uriy Zhuravlev
u.zhuravlev@postgrespro.ru
In reply to: Uriy Zhuravlev (#18)
1 attachment(s)
Re: WIP: Enhanced ALTER OPERATOR

Hello hackers.

Attached is a new version of patch:
* port syntax from NULL to truth NONE
* fix documentation (thanks Heikki)
* rebase to master

Thanks.

--
Uriy Zhuravlev
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

extend_alter_operator_v6.patchtext/x-patch; charset=UTF-8; name=extend_alter_operator_v6.patchDownload
diff --git a/doc/src/sgml/ref/alter_operator.sgml b/doc/src/sgml/ref/alter_operator.sgml
index bdb2d02..237e4f1 100644
--- a/doc/src/sgml/ref/alter_operator.sgml
+++ b/doc/src/sgml/ref/alter_operator.sgml
@@ -26,6 +26,11 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
 
 ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</replaceable> | NONE } , { <replaceable>right_type</replaceable> | NONE } )
                SET SCHEMA <replaceable>new_schema</replaceable>
+
+ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</replaceable> | NONE } , { <replaceable>right_type</replaceable> | NONE } )
+    SET ( {  RESTRICT = { <replaceable class="parameter">res_proc</replaceable> | NONE }
+           | JOIN = { <replaceable class="parameter">join_proc</replaceable> | NONE }
+         } [, ... ] )
 </synopsis>
  </refsynopsisdiv>
 
@@ -34,8 +39,7 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
 
   <para>
    <command>ALTER OPERATOR</command> changes the definition of
-   an operator.  The only currently available functionality is to change the
-   owner of the operator.
+   an operator.
   </para>
 
   <para>
@@ -98,6 +102,25 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+     <term><replaceable class="parameter">res_proc</replaceable></term>
+     <listitem>
+       <para>
+         The restriction selectivity estimator function for this operator; write NONE to remove existing selectivity estimator.
+       </para>
+      </listitem>
+   </varlistentry>
+
+   <varlistentry>
+     <term><replaceable class="parameter">join_proc</replaceable></term>
+     <listitem>
+       <para>
+         The join selectivity estimator function for this operator; write NONE to remove existing selectivity estimator.
+       </para>
+     </listitem>
+   </varlistentry>
+
   </variablelist>
  </refsect1>
 
@@ -109,6 +132,13 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
 <programlisting>
 ALTER OPERATOR @@ (text, text) OWNER TO joe;
 </programlisting></para>
+
+  <para>
+    Change the restriction and join selectivity estimator function of a custom operator <literal>a && b</literal> for type <type>int[]</type>:
+<programlisting>
+ALTER OPERATOR && (_int4, _int4) SET (RESTRICT = _int_contsel, JOIN = _int_contjoinsel);
+</programlisting></para>
+
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 072f530..4c5c9c6 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -28,8 +28,10 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/defrem.h"
 #include "miscadmin.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_func.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -53,7 +55,7 @@ static Oid OperatorShellMake(const char *operatorName,
 				  Oid leftTypeId,
 				  Oid rightTypeId);
 
-static void OperatorUpd(Oid baseId, Oid commId, Oid negId);
+static void ShellOperatorUpd(Oid baseId, Oid commId, Oid negId);
 
 static Oid get_other_operator(List *otherOp,
 				   Oid otherLeftTypeId, Oid otherRightTypeId,
@@ -563,7 +565,7 @@ OperatorCreate(const char *operatorName,
 		commutatorId = operatorObjectId;
 
 	if (OidIsValid(commutatorId) || OidIsValid(negatorId))
-		OperatorUpd(operatorObjectId, commutatorId, negatorId);
+		ShellOperatorUpd(operatorObjectId, commutatorId, negatorId);
 
 	return address;
 }
@@ -633,7 +635,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
 }
 
 /*
- * OperatorUpd
+ * ShellOperatorUpd
  *
  *	For a given operator, look up its negator and commutator operators.
  *	If they are defined, but their negator and commutator fields
@@ -642,7 +644,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
  *	which are the negator or commutator of each other.
  */
 static void
-OperatorUpd(Oid baseId, Oid commId, Oid negId)
+ShellOperatorUpd(Oid baseId, Oid commId, Oid negId)
 {
 	int			i;
 	Relation	pg_operator_desc;
@@ -864,3 +866,154 @@ makeOperatorDependencies(HeapTuple tuple)
 
 	return myself;
 }
+
+/*
+ * Operator update aka ALTER OPERATOR for RESTRICT, JOIN
+ */
+void OperatorUpd(Oid classId,
+				 Oid baseId,
+				 List *operator_params)
+{
+	int			i;
+	ListCell	*pl;
+	Relation	catalog;
+	HeapTuple	tup;
+	Oid 		operator_param_id = 0;
+	bool		nulls[Natts_pg_operator];
+	bool		replaces[Natts_pg_operator];
+	Datum		values[Natts_pg_operator];
+
+	for (i = 0; i < Natts_pg_operator; ++i)
+	{
+		values[i] = (Datum) 0;
+		replaces[i] = false;
+		nulls[i] = false;
+	}
+
+	catalog = heap_open(classId, RowExclusiveLock);
+	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(baseId));
+
+	if (HeapTupleIsValid(tup))
+	{
+		/*
+		 * loop over the definition list and extract the information we need.
+		 */
+		foreach(pl, operator_params)
+		{
+			DefElem    *defel = (DefElem *) lfirst(pl);
+			List	   *param;
+			int			param_type;
+
+			if (defel->arg == NULL)
+				param = NULL;
+			else
+				param = defGetQualifiedName(defel);
+
+			if (pg_strcasecmp(defel->defname, "restrict") == 0)
+				param_type = Anum_pg_operator_oprrest;
+			else if (pg_strcasecmp(defel->defname, "join") == 0)
+				param_type = Anum_pg_operator_oprjoin;
+			else
+			{
+				ereport(WARNING,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("operator attribute \"%s\" not recognized",
+								defel->defname)));
+				continue;
+			}
+
+			/* Resets if written NONE */
+			if (!PointerIsValid(param))
+			{
+				values[param_type - 1] = ObjectIdGetDatum(InvalidOid);
+				replaces[param_type - 1] = true;
+				continue;
+			}
+
+			if (param_type == Anum_pg_operator_oprrest)
+			{
+				Oid			typeId[5];
+				AclResult	aclresult;
+				typeId[0] = INTERNALOID;	/* PlannerInfo */
+				typeId[1] = OIDOID;		/* operator OID */
+				typeId[2] = INTERNALOID;	/* args list */
+				typeId[3] = INT4OID;	/* varRelid */
+
+				operator_param_id = LookupFuncName(param, 4, typeId, false);
+
+				/* estimators must return float8 */
+				if (get_func_rettype(operator_param_id) != FLOAT8OID)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("restriction estimator function %s must return type \"float8\"",
+									NameListToString(param))));
+
+				/* Require EXECUTE rights for the estimator */
+				aclresult = pg_proc_aclcheck(operator_param_id, GetUserId(), ACL_EXECUTE);
+				if (aclresult != ACLCHECK_OK)
+					aclcheck_error(aclresult, ACL_KIND_PROC,
+								   NameListToString(param));
+			}
+			else if (param_type == Anum_pg_operator_oprjoin)
+			{
+				Oid			typeId[5];
+				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.
+				 */
+				operator_param_id = LookupFuncName(param, 5, typeId, true);
+				if (!OidIsValid(operator_param_id))
+					operator_param_id = LookupFuncName(param, 4, typeId, true);
+				/* If not found, reference the 5-argument signature in error msg */
+				if (!OidIsValid(operator_param_id))
+					operator_param_id = LookupFuncName(param, 5, typeId, false);
+
+				/* estimators must return float8 */
+				if (get_func_rettype(operator_param_id) != FLOAT8OID)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("restriction estimator function %s must return type \"float8\"",
+									NameListToString(param))));
+
+				/* Require EXECUTE rights for the estimator */
+				aclresult = pg_proc_aclcheck(operator_param_id, GetUserId(), ACL_EXECUTE);
+				if (aclresult != ACLCHECK_OK)
+					aclcheck_error(aclresult, ACL_KIND_PROC,
+								   NameListToString(param));
+			}
+
+			if (OidIsValid(operator_param_id))
+			{
+				values[param_type - 1] = ObjectIdGetDatum(operator_param_id);
+				replaces[param_type - 1] = true;
+			}
+			else
+				ereport(ERROR,
+					(errmsg_internal("Not found function or operator for alter operator")));
+		}
+
+		/* Update heap */
+		tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(baseId));
+		if (HeapTupleIsValid(tup))
+		{
+			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);
+}
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index b4a1aac..181823b 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -320,3 +320,74 @@ RemoveOperatorById(Oid operOid)
 
 	heap_close(relation, RowExclusiveLock);
 }
+
+ObjectAddress
+ExecAlterOperatorStmt(AlterOperatorStmt *stmt)
+{
+	ObjectAddress address;
+	Relation	catalog;
+	Relation	relation;
+	Datum		datum;
+	Oid			ownerId;
+	bool		isnull;
+	HeapTuple	tup;
+
+	/* Address to be modified operator. */
+	address = get_object_address(OBJECT_OPERATOR,
+								 stmt->object,
+								 stmt->objarg,
+								 &relation,
+								 AccessExclusiveLock,
+								 false);
+	Assert(relation == NULL);
+
+	/* Check user rights. */
+	if (!superuser())
+	{
+		AclObjectKind 	aclkind = get_object_aclkind(address.classId);
+		AttrNumber		Anum_name = get_object_attnum_name(address.classId);
+		AttrNumber		Anum_owner = get_object_attnum_owner(address.classId);;
+
+		catalog = heap_open(address.classId, RowExclusiveLock);
+
+		tup = get_catalog_object_by_oid(catalog, address.objectId);
+
+		if (tup == NULL)
+			elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+				 address.objectId, RelationGetRelationName(catalog));
+
+		datum = heap_getattr(tup, Anum_owner,
+							 RelationGetDescr(catalog), &isnull);
+		Assert(!isnull);
+		ownerId = DatumGetObjectId(datum);
+
+		/* must be owner */
+		if (!has_privs_of_role(GetUserId(), ownerId))
+		{
+			char	   *objname;
+			char		namebuf[NAMEDATALEN];
+
+			if (Anum_name != InvalidAttrNumber)
+			{
+				datum = heap_getattr(tup, Anum_name,
+									 RelationGetDescr(catalog), &isnull);
+				Assert(!isnull);
+				objname = NameStr(*DatumGetName(datum));
+			}
+			else
+			{
+				snprintf(namebuf, sizeof(namebuf), "%u",
+						 HeapTupleGetOid(tup));
+				objname = namebuf;
+			}
+			aclcheck_error(ACLCHECK_NOT_OWNER, aclkind, objname);
+		}
+		heap_close(catalog, RowExclusiveLock);
+	}
+
+	OperatorUpd(address.classId,
+				address.objectId,
+				stmt->defnames);
+
+	return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4c363d3..09cbf88 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3212,6 +3212,19 @@ _copyAlterOwnerStmt(const AlterOwnerStmt *from)
 	return newnode;
 }
 
+static AlterOperatorStmt *
+_copyAlterOperatorStmt(const AlterOperatorStmt *from)
+{
+	AlterOperatorStmt *newnode = makeNode(AlterOperatorStmt);
+
+	COPY_NODE_FIELD(relation);
+	COPY_NODE_FIELD(object);
+	COPY_NODE_FIELD(objarg);
+	COPY_NODE_FIELD(defnames);
+
+	return newnode;
+}
+
 static RuleStmt *
 _copyRuleStmt(const RuleStmt *from)
 {
@@ -4615,6 +4628,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 f19251e..017891a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1339,6 +1339,17 @@ _equalAlterOwnerStmt(const AlterOwnerStmt *a, const AlterOwnerStmt *b)
 }
 
 static bool
+_equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b)
+{
+	COMPARE_NODE_FIELD(relation);
+	COMPARE_NODE_FIELD(object);
+	COMPARE_NODE_FIELD(objarg);
+	COMPARE_NODE_FIELD(defnames);
+
+	return true;
+}
+
+static bool
 _equalRuleStmt(const RuleStmt *a, const RuleStmt *b)
 {
 	COMPARE_NODE_FIELD(relation);
@@ -2980,6 +2991,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 e0ff6f1..40abf22 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 <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
-%type <defelt>	def_elem reloption_elem old_aggr_elem
+%type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
 %type <node>	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
@@ -8198,6 +8199,33 @@ AlterObjectSchemaStmt:
 
 /*****************************************************************************
  *
+ * ALTER OPERATOR name SET define
+ *
+ *****************************************************************************/
+
+AlterOperatorStmt:
+			ALTER OPERATOR any_operator oper_argtypes SET '(' operator_def_list ')'
+				{
+					AlterOperatorStmt *n = makeNode(AlterOperatorStmt);
+					n->object = $3;
+					n->objarg = $4;
+					n->defnames = $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 0dabcc1..cf60c7c 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:
@@ -861,6 +862,19 @@ standard_ProcessUtility(Node *parsetree,
 			}
 			break;
 
+		case T_AlterOperatorStmt:
+			{
+				AlterOperatorStmt *stmt = (AlterOperatorStmt *) parsetree;
+				if (EventTriggerSupportsObjectType(OBJECT_OPERATOR)) {
+					ProcessUtilitySlow(parsetree, queryString,
+									   context, params,
+									   dest, completionTag);
+				}
+				else
+					ExecAlterOperatorStmt(stmt);
+			}
+			break;
+
 		case T_CommentStmt:
 			{
 				CommentStmt *stmt = (CommentStmt *) parsetree;
@@ -1481,6 +1495,10 @@ ProcessUtilitySlow(Node *parsetree,
 				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_AlterOperatorStmt:
+				address = ExecAlterOperatorStmt((AlterOperatorStmt *) parsetree);
+				break;
+
 			case T_CommentStmt:
 				address = CommentObject((CommentStmt *) parsetree);
 				break;
@@ -2494,6 +2512,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/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 26c9d4e..67420b1 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1837,4 +1837,8 @@ extern ObjectAddress OperatorCreate(const char *operatorName,
 			   bool canMerge,
 			   bool canHash);
 
+extern void OperatorUpd(Oid classId,
+		Oid baseId,
+		List *operator_params);
+
 #endif   /* PG_OPERATOR_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 9b81c16..e9396d3 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 ExecAlterOperatorStmt(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 290cdb3..f8acda4 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 868905b..c20868f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2543,6 +2543,20 @@ typedef struct AlterOwnerStmt
 
 
 /* ----------------------
+ *		Alter Operator Set Restrict, Join
+ * ----------------------
+ */
+typedef struct AlterOperatorStmt
+{
+	NodeTag		type;
+	RangeVar   *relation;		/* in case it's a table */
+	List	   *object;			/* in case it's some other object */
+	List	   *objarg;			/* argument types, if applicable */
+	List	   *defnames;		/* operator */
+} 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 0000000..3da130e
--- /dev/null
+++ b/src/test/regress/expected/alter_operator.out
@@ -0,0 +1,78 @@
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+    SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = fn_op2,
+    COMMUTATOR = ===,
+    NEGATOR = !==,
+    RESTRICT = contsel,
+    JOIN = contjoinsel,
+    SORT1, SORT2, LTCMP, GTCMP, 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 = 16 AND oprright = 16;
+ 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 = 16 AND oprright = 16;
+ 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 = 16 AND oprright = 16;
+ oprrest | oprjoin 
+---------+---------
+ -       | -
+(1 row)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel,
+										   JOIN = contjoinsel);
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+ oprrest |   oprjoin   
+---------+-------------
+ contsel | contjoinsel
+(1 row)
+
+--
+-- Trying set the wrong parameters
+--
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+WARNING:  operator attribute "commutator" not recognized
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+WARNING:  operator attribute "negator" not recognized
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = blabla);
+ERROR:  function blabla(internal, oid, internal, integer) does not exist
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = blabla);
+ERROR:  function blabla(internal, oid, internal, smallint, internal) does not exist
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
+WARNING:  operator attribute "commutator" not recognized
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+WARNING:  operator attribute "negator" not recognized
+--
+-- Trying set params from wrong user
+--
+CREATE USER regression_user;
+SET SESSION AUTHORIZATION regression_user;
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
+ERROR:  must be owner of operator ===
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NONE);
+ERROR:  must be owner of operator ===
+RESET SESSION AUTHORIZATION;
+DROP USER regression_user;
+DROP OPERATOR === (boolean, boolean);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 91780cd..836f9f9 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml alter_operator
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index a2e0ceb..fbec844 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -146,6 +146,7 @@ test: without_oid
 test: conversion
 test: truncate
 test: alter_table
+test: alter_operator
 test: sequence
 test: polymorphism
 test: rowtypes
diff --git a/src/test/regress/sql/alter_operator.sql b/src/test/regress/sql/alter_operator.sql
new file mode 100644
index 0000000..59698f9
--- /dev/null
+++ b/src/test/regress/sql/alter_operator.sql
@@ -0,0 +1,68 @@
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+    SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+    LEFTARG = boolean,
+    RIGHTARG = boolean,
+    PROCEDURE = fn_op2,
+    COMMUTATOR = ===,
+    NEGATOR = !==,
+    RESTRICT = contsel,
+    JOIN = contjoinsel,
+    SORT1, SORT2, LTCMP, GTCMP, 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 = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE,
+										   JOIN = NONE);
+
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel,
+										   JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin
+FROM pg_operator WHERE oprname = '===' AND oprleft = 16 AND oprright = 16;
+
+--
+-- Trying set the wrong parameters
+--
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = blabla);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = blabla);
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+
+
+--
+-- Trying set params from wrong user
+--
+
+CREATE USER regression_user;
+SET SESSION AUTHORIZATION regression_user;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NONE);
+
+RESET SESSION AUTHORIZATION;
+DROP USER regression_user;
+
+DROP OPERATOR === (boolean, boolean);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 4b650d1..d86072f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -70,6 +70,7 @@ AlterFunctionStmt
 AlterObjectSchemaStmt
 AlterOpFamilyStmt
 AlterOwnerStmt
+AlterOperatorStmt
 AlterPolicyStmt
 AlterRoleSetStmt
 AlterRoleStmt
#22Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Uriy Zhuravlev (#21)
Re: WIP: Enhanced ALTER OPERATOR

On 07/13/2015 03:43 PM, Uriy Zhuravlev wrote:

Hello hackers.

Attached is a new version of patch:
* port syntax from NULL to truth NONE
* fix documentation (thanks Heikki)
* rebase to master

Thanks, committed after some editing. I put all the logic into
AlterOperator function, in operatorcmds.c, rather than pg_operator.c. I
think that's how we've lately organized commands. I also simplified the
code quite a bit - I think you had copy-pasted from one of the generic
ALTER variants, like AlterOwnerStmt, which was overly generic for ALTER
OPERATOR. No need to look up the owner/name attributes dynamically, etc.

There was one genuine bug that I fixed: the ALTER OPERATOR command
didn't check all the same conditions that CREATE OPERATOR did, namely
that only binary operators can have join selectivity, and that only
boolean operators can have restriction/join selectivity.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Uriy Zhuravlev
u.zhuravlev@postgrespro.ru
In reply to: Heikki Linnakangas (#22)
Re: WIP: Enhanced ALTER OPERATOR

On Tuesday 14 July 2015 18:22:26 you wrote:

I think you had copy-pasted from one of the generic
ALTER variants, like AlterOwnerStmt, which was overly generic for ALTER
OPERATOR.

You right.

Thanks, committed after some editing.

Thanks you. And it BIG editing. ;)

After that, can you view my next patch?^_^
https://commitfest.postgresql.org/5/253/

--
Uriy Zhuravlev
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Jeff Janes
jeff.janes@gmail.com
In reply to: Heikki Linnakangas (#22)
Re: WIP: Enhanced ALTER OPERATOR

On Tue, Jul 14, 2015 at 8:22 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 07/13/2015 03:43 PM, Uriy Zhuravlev wrote:

Hello hackers.

Attached is a new version of patch:
* port syntax from NULL to truth NONE
* fix documentation (thanks Heikki)
* rebase to master

Thanks, committed after some editing. I put all the logic into
AlterOperator function, in operatorcmds.c, rather than pg_operator.c. I
think that's how we've lately organized commands. I also simplified the
code quite a bit - I think you had copy-pasted from one of the generic
ALTER variants, like AlterOwnerStmt, which was overly generic for ALTER
OPERATOR. No need to look up the owner/name attributes dynamically, etc.

There was one genuine bug that I fixed: the ALTER OPERATOR command didn't
check all the same conditions that CREATE OPERATOR did, namely that only
binary operators can have join selectivity, and that only boolean operators
can have restriction/join selectivity.

I'm getting some compiler warnings now:

operatorcmds.c: In function 'AlterOperator':
operatorcmds.c:504: warning: 'address.objectSubId' may be used
uninitialized in this function
operatorcmds.c:365: note: 'address.objectSubId' was declared here
operatorcmds.c:504: warning: 'address.objectId' may be used uninitialized
in this function
operatorcmds.c:365: note: 'address.objectId' was declared here
operatorcmds.c:504: warning: 'address.classId' may be used uninitialized in
this function
operatorcmds.c:365: note: 'address.classId' was declared here

gcc version 4.4.7 20120313 (Red Hat 4.4.7-11) (GCC)

Thanks,

Jeff

#25Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Jeff Janes (#24)
Re: WIP: Enhanced ALTER OPERATOR

On 07/14/2015 07:28 PM, Jeff Janes wrote:

I'm getting some compiler warnings now:

operatorcmds.c: In function 'AlterOperator':
operatorcmds.c:504: warning: 'address.objectSubId' may be used
uninitialized in this function
operatorcmds.c:365: note: 'address.objectSubId' was declared here
operatorcmds.c:504: warning: 'address.objectId' may be used uninitialized
in this function
operatorcmds.c:365: note: 'address.objectId' was declared here
operatorcmds.c:504: warning: 'address.classId' may be used uninitialized in
this function
operatorcmds.c:365: note: 'address.classId' was declared here

gcc version 4.4.7 20120313 (Red Hat 4.4.7-11) (GCC)

Ah, thanks, fixed. I was apparently compiling with -O0.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers