Re: [PATCH] fix DROP OPERATOR to reset links to itself on commutator and negator

Started by Roma Sokolovalmost 10 years ago8 messages
#1Roma Sokolov
sokolov.r.v@gmail.com
1 attachment(s)

Hi,

Tomas, thanks for review and comments!

I have to admit I find the existing code a bit convoluted, particularly the part that deals with the (commId == negId) case. And the patch does not really improve the situation, quite the contrary.

Perhaps it’s time to get rid of this optimization?

Indeed, code in OperatorUpd is not very easy to read, due to handling this case.
However we can achieve the same results without too much duplication. I have
changed OperatorUpd to perform tuple modification in "lazy" way. Please, check
it out in v4.patch (attached).

Also, maybe I'm missing something obvious, but it's not immediately obvious to me why we're only checking oprcom and not oprnegate? I.e. why shouldn’t the code be

We do not need to check for operOid != op->oprnegate, since we can't create
operator that is negator to itself. Thus, opnergate either present and differs
from operator being deleted, or is InvalidOid. I have added some clarification
in the comment for future readers.

Fixed style issues as well.

Cheers,
Roma

Attachments:

fix_drop_operator_reset_oprcom_and_oprnegate_fields_v4.patchapplication/octet-stream; name=fix_drop_operator_reset_oprcom_and_oprnegate_fields_v4.patchDownload
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 3cd1899..e774571 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -54,8 +54,6 @@ static Oid OperatorShellMake(const char *operatorName,
 				  Oid leftTypeId,
 				  Oid rightTypeId);
 
-static void OperatorUpd(Oid baseId, Oid commId, Oid negId);
-
 static Oid get_other_operator(List *otherOp,
 				   Oid otherLeftTypeId, Oid otherRightTypeId,
 				   const char *operatorName, Oid operatorNamespace,
@@ -566,7 +564,7 @@ OperatorCreate(const char *operatorName,
 		commutatorId = operatorObjectId;
 
 	if (OidIsValid(commutatorId) || OidIsValid(negatorId))
-		OperatorUpd(operatorObjectId, commutatorId, negatorId);
+		OperatorUpd(operatorObjectId, commutatorId, negatorId, false);
 
 	return address;
 }
@@ -643,9 +641,14 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
  *	(respectively) are empty, then use the new operator for neg or comm.
  *	This solves a problem for users who need to insert two new operators
  *	which are the negator or commutator of each other.
+ *
+ *	If we are removing given operator, we'd like to reset links on commutator
+ *	and negator, but only if they are point to given operator (i.e. haven't
+ *	been somehow updated. This check is mostly useless, since we forbid
+ *	overwriting oprcom and oprnegate).
  */
-static void
-OperatorUpd(Oid baseId, Oid commId, Oid negId)
+void
+OperatorUpd(Oid baseId, Oid commId, Oid negId, bool isDelete)
 {
 	int			i;
 	Relation	pg_operator_desc;
@@ -653,6 +656,9 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 	bool		nulls[Natts_pg_operator];
 	bool		replaces[Natts_pg_operator];
 	Datum		values[Natts_pg_operator];
+	Datum		target;
+	bool		updateTuple;
+
 
 	for (i = 0; i < Natts_pg_operator; ++i)
 	{
@@ -660,104 +666,108 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 		replaces[i] = false;
 		nulls[i] = false;
 	}
-
+	updateTuple = false;
+	
 	/*
 	 * check and update the commutator & negator, if necessary
 	 *
 	 * We need a CommandCounterIncrement here in case of a self-commutator
 	 * operator: we'll need to update the tuple that we just inserted.
+	 * If we're deleting operator, we will not do any updates for
+	 * self-commutator: we will just delete it.
 	 */
-	CommandCounterIncrement();
+	if (!isDelete)
+		CommandCounterIncrement();
 
 	pg_operator_desc = heap_open(OperatorRelationId, RowExclusiveLock);
 
+	/*
+	 * When deleting, reset other operator field to InvalidOid, otherwise,
+     * set it to point to operator being updated.
+	 */
+	target = (isDelete ? InvalidOid : ObjectIdGetDatum(baseId));
+
 	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(commId));
 
 	/*
-	 * if the commutator and negator are the same operator, do one update. XXX
-	 * this is probably useless code --- I doubt it ever makes sense for
-	 * commutator and negator to be the same thing...
+	 * if the commutator and negator are the same operator, as for example,
+	 * comparison operators, like < and >, we will do one update. Otherwise
+	 * we will update other operators separately, with two modifications.
 	 */
-	if (commId == negId)
+
+	if (HeapTupleIsValid(tup))
 	{
-		if (HeapTupleIsValid(tup))
+		Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup);
+		/*
+		 * When deleting, check, whether other op field points to operator
+		 * being updated. Otherwise, check, whether field is not set.
+		 */
+		if (isDelete ? t->oprcom == baseId : !OidIsValid(t->oprcom))
 		{
-			Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup);
-
-			if (!OidIsValid(t->oprcom) || !OidIsValid(t->oprnegate))
-			{
-				if (!OidIsValid(t->oprnegate))
-				{
-					values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(baseId);
-					replaces[Anum_pg_operator_oprnegate - 1] = true;
-				}
-
-				if (!OidIsValid(t->oprcom))
-				{
-					values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(baseId);
-					replaces[Anum_pg_operator_oprcom - 1] = true;
-				}
-
-				tup = heap_modify_tuple(tup,
-										RelationGetDescr(pg_operator_desc),
-										values,
-										nulls,
-										replaces);
-
-				simple_heap_update(pg_operator_desc, &tup->t_self, tup);
-
-				CatalogUpdateIndexes(pg_operator_desc, tup);
-			}
+			values[Anum_pg_operator_oprcom - 1] = target;
+			replaces[Anum_pg_operator_oprcom - 1] = true;
+			updateTuple = true;
 		}
-
-		heap_close(pg_operator_desc, RowExclusiveLock);
-
-		return;
 	}
-
-	/* if commutator and negator are different, do two updates */
-
-	if (HeapTupleIsValid(tup) &&
-		!(OidIsValid(((Form_pg_operator) GETSTRUCT(tup))->oprcom)))
+	/*
+	 * If the commutator and negator are different, check whether we need
+	 * update tuple and retrieve tuple for negator op. Otherwise, continue
+	 * with the same tuple for negator.
+	 */
+	if (commId != negId)
 	{
-		values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(baseId);
-		replaces[Anum_pg_operator_oprcom - 1] = true;
-
-		tup = heap_modify_tuple(tup,
-								RelationGetDescr(pg_operator_desc),
-								values,
-								nulls,
-								replaces);
+		/*
+		 * If we should update tuple, issue update and reset values and replaces
+		 * for possible subsequent update.
+		 */
+		if (updateTuple)
+		{
+			tup = heap_modify_tuple(tup,
+									RelationGetDescr(pg_operator_desc),
+									values,
+									nulls,
+									replaces);
 
-		simple_heap_update(pg_operator_desc, &tup->t_self, tup);
+			simple_heap_update(pg_operator_desc, &tup->t_self, tup);
 
-		CatalogUpdateIndexes(pg_operator_desc, tup);
+			CatalogUpdateIndexes(pg_operator_desc, tup);
 
-		values[Anum_pg_operator_oprcom - 1] = (Datum) NULL;
-		replaces[Anum_pg_operator_oprcom - 1] = false;
+			values[Anum_pg_operator_oprcom - 1] = (Datum) NULL;
+			replaces[Anum_pg_operator_oprcom - 1] = false;
+			updateTuple = false;
+		}
+		tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(negId));
 	}
 
-	/* check and update the negator, if necessary */
-
-	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(negId));
-
-	if (HeapTupleIsValid(tup) &&
-		!(OidIsValid(((Form_pg_operator) GETSTRUCT(tup))->oprnegate)))
+	/* check, whether we should update the negator */
+	if (HeapTupleIsValid(tup))
 	{
-		values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(baseId);
-		replaces[Anum_pg_operator_oprnegate - 1] = true;
+		Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup);
+		if (isDelete ? t->oprnegate == baseId : !OidIsValid(t->oprnegate))
+		{
+			values[Anum_pg_operator_oprnegate - 1] = target;
+			replaces[Anum_pg_operator_oprnegate - 1] = true;
+			updateTuple = true;
+			
+		}
+	}
 
+	/*
+	 * Here we either should update tuple for commutator and negator,
+	 * or just tuple for negator.
+	 */
+	if (updateTuple)
+	{
 		tup = heap_modify_tuple(tup,
 								RelationGetDescr(pg_operator_desc),
 								values,
 								nulls,
 								replaces);
-
 		simple_heap_update(pg_operator_desc, &tup->t_self, tup);
 
 		CatalogUpdateIndexes(pg_operator_desc, tup);
 	}
-
+	
 	heap_close(pg_operator_desc, RowExclusiveLock);
 }
 
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index 664e5d7..af95b9a 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -339,15 +339,32 @@ ValidateJoinEstimator(List *joinName)
 void
 RemoveOperatorById(Oid operOid)
 {
-	Relation	relation;
-	HeapTuple	tup;
+	Relation			relation;
+	HeapTuple			tup;
+	Form_pg_operator	op;
 
 	relation = heap_open(OperatorRelationId, RowExclusiveLock);
 
 	tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid));
+	op = (Form_pg_operator) GETSTRUCT(tup);
 	if (!HeapTupleIsValid(tup)) /* should not happen */
 		elog(ERROR, "cache lookup failed for operator %u", operOid);
 
+	/*
+	 * Reset links on commutator and negator. Only do that if either
+	 * oprcom or oprnegate is set and given operator is not self-commutator.
+	 * For self-commutator with negator, prevent meaningful updates of the
+	 * same tuple by sending InvalidOid.
+	 * Since operator can't be its own negator, we don't need this check for
+	 * negator: if there is oprnegate, it should be updated.
+	 */
+	if (OidIsValid(op->oprnegate) ||
+		(OidIsValid(op->oprcom) && operOid != op->oprcom))
+		OperatorUpd(operOid,
+					operOid == op->oprcom ? InvalidOid : op->oprcom,
+					op->oprnegate,
+					true);
+
 	simple_heap_delete(relation, &tup->t_self);
 
 	ReleaseSysCache(tup);
diff --git a/src/include/catalog/pg_operator_fn.h b/src/include/catalog/pg_operator_fn.h
index 5315e19..3e1c7ae 100644
--- a/src/include/catalog/pg_operator_fn.h
+++ b/src/include/catalog/pg_operator_fn.h
@@ -30,5 +30,5 @@ extern ObjectAddress OperatorCreate(const char *operatorName,
 			   bool canHash);
 
 extern ObjectAddress makeOperatorDependencies(HeapTuple tuple, bool isUpdate);
-
+extern void OperatorUpd(Oid baseId, Oid commId, Oid negId, bool isDelete);
 #endif   /* PG_OPERATOR_FN_H */
diff --git a/src/test/regress/expected/drop_operator.out b/src/test/regress/expected/drop_operator.out
new file mode 100644
index 0000000..cc8f5e7
--- /dev/null
+++ b/src/test/regress/expected/drop_operator.out
@@ -0,0 +1,61 @@
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        COMMUTATOR = ===
+);
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===,
+        COMMUTATOR = !==
+);
+DROP OPERATOR !==(bigint, bigint);
+SELECT  ctid, oprcom
+FROM    pg_catalog.pg_operator fk
+WHERE   oprcom != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom);
+ ctid | oprcom 
+------+--------
+(0 rows)
+
+SELECT  ctid, oprnegate
+FROM    pg_catalog.pg_operator fk
+WHERE   oprnegate != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprnegate);
+ ctid | oprnegate 
+------+-----------
+(0 rows)
+
+DROP OPERATOR ===(bigint, bigint);
+CREATE OPERATOR <| (
+        PROCEDURE = int8lt,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+CREATE OPERATOR |> (
+        PROCEDURE = int8gt,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = <|,
+        COMMUTATOR = <|
+);
+DROP OPERATOR |>(bigint, bigint);
+SELECT  ctid, oprcom
+FROM    pg_catalog.pg_operator fk
+WHERE   oprcom != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom);
+ ctid | oprcom 
+------+--------
+(0 rows)
+
+SELECT  ctid, oprnegate
+FROM    pg_catalog.pg_operator fk
+WHERE   oprnegate != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprnegate);
+ ctid | oprnegate 
+------+-----------
+(0 rows)
+
+DROP OPERATOR <|(bigint, bigint);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index bec0316..731677f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -79,7 +79,7 @@ ignore: random
 # ----------
 # Another group of parallel tests
 # ----------
-test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete
+test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete drop_operator
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 7e9b319..4e2fe83 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -115,6 +115,7 @@ test: object_address
 test: tablesample
 test: alter_generic
 test: alter_operator
+test: drop_operator
 test: misc
 test: psql
 test: async
diff --git a/src/test/regress/sql/drop_operator.sql b/src/test/regress/sql/drop_operator.sql
new file mode 100644
index 0000000..cc62cfa
--- /dev/null
+++ b/src/test/regress/sql/drop_operator.sql
@@ -0,0 +1,56 @@
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        COMMUTATOR = ===
+);
+
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===,
+        COMMUTATOR = !==
+);
+
+DROP OPERATOR !==(bigint, bigint);
+
+SELECT  ctid, oprcom
+FROM    pg_catalog.pg_operator fk
+WHERE   oprcom != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom);
+
+SELECT  ctid, oprnegate
+FROM    pg_catalog.pg_operator fk
+WHERE   oprnegate != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprnegate);
+
+DROP OPERATOR ===(bigint, bigint);
+
+CREATE OPERATOR <| (
+        PROCEDURE = int8lt,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+
+CREATE OPERATOR |> (
+        PROCEDURE = int8gt,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = <|,
+        COMMUTATOR = <|
+);
+
+DROP OPERATOR |>(bigint, bigint);
+
+SELECT  ctid, oprcom
+FROM    pg_catalog.pg_operator fk
+WHERE   oprcom != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom);
+
+SELECT  ctid, oprnegate
+FROM    pg_catalog.pg_operator fk
+WHERE   oprnegate != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprnegate);
+
+DROP OPERATOR <|(bigint, bigint);
#2Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Roma Sokolov (#1)

Hi,

On 03/23/2016 12:50 AM, Roma Sokolov wrote:

Hi,

Tomas, thanks for review and comments!

I have to admit I find the existing code a bit convoluted,
particularly the part that deals with the (commId == negId) case.
And the patch does not really improve the situation, quite the
contrary. Perhaps it’s time to get rid of this optimization?

Indeed, code in OperatorUpd is not very easy to read, due to
handling this case. However we can achieve the same results without
too much duplication. I have changed OperatorUpd to perform tuple
modification in "lazy" way. Please, check it out in v4.patch
(attached).

OK, the new code seems more comprehensible to me.

Also, maybe I'm missing something obvious, but it's not
immediately obvious to me why we're only checking oprcom and not
oprnegate? I.e. why shouldn’t the code be

We do not need to check for operOid != op->oprnegate, since we can't
create operator that is negator to itself. Thus, opnergate either
present and differs from operator being deleted, or is InvalidOid. I
have added some clarification in the comment for future readers.

Ah, I see. Thanks for the clarification.

Fixed style issues as well.

I've noticed some whitespace issues in the OperatorUpd function - there
are two or three lines with just a tabulator at the beginning, and one
comment mixes indentation by tabs with spaces.

Also, it's generally recommended no to tweak formatting when not
necessary, so perhaps don't remove the empty line at the end of the
function (before simple_heap_update).

I think the comments will need rewording, but I'll leave that to a
native speaker.

regards
Tomas

--
Tomas Vondra 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

#3Robert Haas
robertmhaas@gmail.com
In reply to: Tomas Vondra (#2)
1 attachment(s)

On Tue, Mar 22, 2016 at 9:25 PM, Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

OK, the new code seems more comprehensible to me.

I did not find this version very clear. It wasn't consistent about
using ObjectIdGetDatum() where needed, but the bigger problem was that
I found the logic unnecessarily convoluted. I rewrote it - I believe
more straightforwardly - as attached. How does this look?

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

Attachments:

fix_drop_operator_reset_oprcom_and_oprnegate_fields_v5.patchtext/x-diff; charset=US-ASCII; name=fix_drop_operator_reset_oprcom_and_oprnegate_fields_v5.patchDownload
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 3cd1899..cd66823 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -54,8 +54,6 @@ static Oid OperatorShellMake(const char *operatorName,
 				  Oid leftTypeId,
 				  Oid rightTypeId);
 
-static void OperatorUpd(Oid baseId, Oid commId, Oid negId);
-
 static Oid get_other_operator(List *otherOp,
 				   Oid otherLeftTypeId, Oid otherRightTypeId,
 				   const char *operatorName, Oid operatorNamespace,
@@ -566,7 +564,7 @@ OperatorCreate(const char *operatorName,
 		commutatorId = operatorObjectId;
 
 	if (OidIsValid(commutatorId) || OidIsValid(negatorId))
-		OperatorUpd(operatorObjectId, commutatorId, negatorId);
+		OperatorUpd(operatorObjectId, commutatorId, negatorId, false);
 
 	return address;
 }
@@ -639,125 +637,158 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
  * OperatorUpd
  *
  *	For a given operator, look up its negator and commutator operators.
- *	If they are defined, but their negator and commutator fields
- *	(respectively) are empty, then use the new operator for neg or comm.
- *	This solves a problem for users who need to insert two new operators
- *	which are the negator or commutator of each other.
+ *	When isDelete is false, update their negator and commutator operators to
+ *	point back to the given operator; when isDelete is true, update those
+ *	operators to no longer point back to the given operator.
+ *
+ *	The !isDelete case solves a problem for users who need to insert two new
+ *  operators which are the negator or commutator of each other, while the
+ *  isDelete case is important so as not to leave dangling OID links behind
+ *  after dropping an operator.
  */
-static void
-OperatorUpd(Oid baseId, Oid commId, Oid negId)
+void
+OperatorUpd(Oid baseId, Oid commId, Oid negId, bool isDelete)
 {
-	int			i;
 	Relation	pg_operator_desc;
-	HeapTuple	tup;
-	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;
-	}
+	HeapTuple	tup = NULL;
 
 	/*
-	 * check and update the commutator & negator, if necessary
-	 *
-	 * We need a CommandCounterIncrement here in case of a self-commutator
-	 * operator: we'll need to update the tuple that we just inserted.
+	 * If we're making an operator into its own commutator, then we need a
+	 * command-counter increment here, since we've just inserted the tuple
+	 * we're about to update.  But when we're dropping an operator, we can
+	 * skip this.
 	 */
-	CommandCounterIncrement();
+	if (!isDelete)
+		CommandCounterIncrement();
 
+	/* Open the relation. */
 	pg_operator_desc = heap_open(OperatorRelationId, RowExclusiveLock);
 
-	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(commId));
+	/* Get a copy of the commutator's tuple. */
+	if (OidIsValid(commId))
+		tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(commId));
 
-	/*
-	 * if the commutator and negator are the same operator, do one update. XXX
-	 * this is probably useless code --- I doubt it ever makes sense for
-	 * commutator and negator to be the same thing...
-	 */
-	if (commId == negId)
+	/* Update the commutator's tuple if need be. */
+	if (HeapTupleIsValid(tup))
 	{
-		if (HeapTupleIsValid(tup))
+		Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup);
+		bool		update_commutator = false;
+		bool		nulls[Natts_pg_operator];
+		bool		replaces[Natts_pg_operator];
+		Datum		values[Natts_pg_operator];
+
+		memset(values, 0, sizeof(values));
+		memset(replaces, 0, sizeof(replaces));
+		memset(nulls, 0, sizeof(nulls));
+
+		/*
+		 * Out of due caution, we only change the commutator's orpcom field
+		 * if it has the exact value we expected: InvalidOid when creating an
+		 * operator, and baseId when dropping one.
+		 */
+		if (isDelete && t->oprcom == baseId)
 		{
-			Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup);
+			values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(InvalidOid);
+			replaces[Anum_pg_operator_oprcom - 1] = true;
+			update_commutator = true;
+		}
+		else if (!isDelete && !OidIsValid(t->oprcom))
+		{
+			values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(baseId);
+			replaces[Anum_pg_operator_oprcom - 1] = true;
+			update_commutator = true;
+		}
 
-			if (!OidIsValid(t->oprcom) || !OidIsValid(t->oprnegate))
+		/*
+		 * If the commutator is also the negator, which doesn't really make
+		 * sense but could perhaps happen, then both updates apply to the same
+		 * tuple and we should do just one update.  Handle that corner case
+		 * here.
+		 */
+		if (commId == negId)
+		{
+			if (isDelete && t->oprnegate == baseId)
+			{
+				values[Anum_pg_operator_oprnegate - 1] =
+					ObjectIdGetDatum(InvalidOid);
+				replaces[Anum_pg_operator_oprnegate - 1] = true;
+				update_commutator = true;
+			}
+			else if (!isDelete && !OidIsValid(t->oprnegate))
 			{
-				if (!OidIsValid(t->oprnegate))
-				{
-					values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(baseId);
-					replaces[Anum_pg_operator_oprnegate - 1] = true;
-				}
-
-				if (!OidIsValid(t->oprcom))
-				{
-					values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(baseId);
-					replaces[Anum_pg_operator_oprcom - 1] = true;
-				}
-
-				tup = heap_modify_tuple(tup,
-										RelationGetDescr(pg_operator_desc),
-										values,
-										nulls,
-										replaces);
-
-				simple_heap_update(pg_operator_desc, &tup->t_self, tup);
-
-				CatalogUpdateIndexes(pg_operator_desc, tup);
+				values[Anum_pg_operator_oprnegate - 1] =
+					ObjectIdGetDatum(baseId);
+				replaces[Anum_pg_operator_oprnegate - 1] = true;
+				update_commutator = true;
 			}
 		}
 
-		heap_close(pg_operator_desc, RowExclusiveLock);
-
-		return;
-	}
-
-	/* if commutator and negator are different, do two updates */
-
-	if (HeapTupleIsValid(tup) &&
-		!(OidIsValid(((Form_pg_operator) GETSTRUCT(tup))->oprcom)))
-	{
-		values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(baseId);
-		replaces[Anum_pg_operator_oprcom - 1] = true;
-
-		tup = heap_modify_tuple(tup,
-								RelationGetDescr(pg_operator_desc),
-								values,
-								nulls,
-								replaces);
-
-		simple_heap_update(pg_operator_desc, &tup->t_self, tup);
-
-		CatalogUpdateIndexes(pg_operator_desc, tup);
-
-		values[Anum_pg_operator_oprcom - 1] = (Datum) NULL;
-		replaces[Anum_pg_operator_oprcom - 1] = false;
+		/* If any columns were found to need modification, update tuple. */
+		if (update_commutator)
+		{
+			tup = heap_modify_tuple(tup,
+									RelationGetDescr(pg_operator_desc),
+									values,
+									nulls,
+									replaces);
+			simple_heap_update(pg_operator_desc, &tup->t_self, tup);
+			CatalogUpdateIndexes(pg_operator_desc, tup);
+		}
 	}
 
-	/* check and update the negator, if necessary */
-
-	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(negId));
-
-	if (HeapTupleIsValid(tup) &&
-		!(OidIsValid(((Form_pg_operator) GETSTRUCT(tup))->oprnegate)))
+	/*
+	 * Assuming (as will almost always be the case) that the commutator and
+	 * negator are different, we now need to update the negator's tuple,
+	 * assuming there is a negator.
+	 */
+	tup = NULL;
+	if (OidIsValid(negId) && commId != negId)
+		tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(negId));
+	if (HeapTupleIsValid(tup))
 	{
-		values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(baseId);
-		replaces[Anum_pg_operator_oprnegate - 1] = true;
+		Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup);
+		bool		update_negator = false;
+		bool		nulls[Natts_pg_operator];
+		bool		replaces[Natts_pg_operator];
+		Datum		values[Natts_pg_operator];
 
-		tup = heap_modify_tuple(tup,
-								RelationGetDescr(pg_operator_desc),
-								values,
-								nulls,
-								replaces);
+		memset(values, 0, sizeof(values));
+		memset(replaces, 0, sizeof(replaces));
+		memset(nulls, 0, sizeof(nulls));
 
-		simple_heap_update(pg_operator_desc, &tup->t_self, tup);
+		/*
+		 * Out of due caution, we only change the negators's orpneg field
+		 * if it has the exact value we expected: InvalidOid when creating an
+		 * operator, and baseId when dropping one.
+		 */
+		if (isDelete && t->oprnegate == baseId)
+		{
+			values[Anum_pg_operator_oprnegate - 1] =
+				ObjectIdGetDatum(InvalidOid);
+			replaces[Anum_pg_operator_oprnegate - 1] = true;
+			update_negator = true;
+		}
+		else if (!isDelete && !OidIsValid(t->oprnegate))
+		{
+			values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(baseId);
+			replaces[Anum_pg_operator_oprnegate - 1] = true;
+			update_negator = true;
+		}
 
-		CatalogUpdateIndexes(pg_operator_desc, tup);
+		/* If any columns were found to need modification, update tuple. */
+		if (update_negator)
+		{
+			tup = heap_modify_tuple(tup,
+									RelationGetDescr(pg_operator_desc),
+									values,
+									nulls,
+									replaces);
+			simple_heap_update(pg_operator_desc, &tup->t_self, tup);
+			CatalogUpdateIndexes(pg_operator_desc, tup);
+		}
 	}
 
+	/* Close relation and release catalog lock */
 	heap_close(pg_operator_desc, RowExclusiveLock);
 }
 
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index 664e5d7..af95b9a 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -339,15 +339,32 @@ ValidateJoinEstimator(List *joinName)
 void
 RemoveOperatorById(Oid operOid)
 {
-	Relation	relation;
-	HeapTuple	tup;
+	Relation			relation;
+	HeapTuple			tup;
+	Form_pg_operator	op;
 
 	relation = heap_open(OperatorRelationId, RowExclusiveLock);
 
 	tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid));
+	op = (Form_pg_operator) GETSTRUCT(tup);
 	if (!HeapTupleIsValid(tup)) /* should not happen */
 		elog(ERROR, "cache lookup failed for operator %u", operOid);
 
+	/*
+	 * Reset links on commutator and negator. Only do that if either
+	 * oprcom or oprnegate is set and given operator is not self-commutator.
+	 * For self-commutator with negator, prevent meaningful updates of the
+	 * same tuple by sending InvalidOid.
+	 * Since operator can't be its own negator, we don't need this check for
+	 * negator: if there is oprnegate, it should be updated.
+	 */
+	if (OidIsValid(op->oprnegate) ||
+		(OidIsValid(op->oprcom) && operOid != op->oprcom))
+		OperatorUpd(operOid,
+					operOid == op->oprcom ? InvalidOid : op->oprcom,
+					op->oprnegate,
+					true);
+
 	simple_heap_delete(relation, &tup->t_self);
 
 	ReleaseSysCache(tup);
diff --git a/src/include/catalog/pg_operator_fn.h b/src/include/catalog/pg_operator_fn.h
index 5315e19..eb8d011 100644
--- a/src/include/catalog/pg_operator_fn.h
+++ b/src/include/catalog/pg_operator_fn.h
@@ -30,5 +30,6 @@ extern ObjectAddress OperatorCreate(const char *operatorName,
 			   bool canHash);
 
 extern ObjectAddress makeOperatorDependencies(HeapTuple tuple, bool isUpdate);
+extern void OperatorUpd(Oid baseId, Oid commId, Oid negId, bool isDelete);
 
 #endif   /* PG_OPERATOR_FN_H */
diff --git a/src/test/regress/expected/drop_operator.out b/src/test/regress/expected/drop_operator.out
new file mode 100644
index 0000000..cc8f5e7
--- /dev/null
+++ b/src/test/regress/expected/drop_operator.out
@@ -0,0 +1,61 @@
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        COMMUTATOR = ===
+);
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===,
+        COMMUTATOR = !==
+);
+DROP OPERATOR !==(bigint, bigint);
+SELECT  ctid, oprcom
+FROM    pg_catalog.pg_operator fk
+WHERE   oprcom != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom);
+ ctid | oprcom 
+------+--------
+(0 rows)
+
+SELECT  ctid, oprnegate
+FROM    pg_catalog.pg_operator fk
+WHERE   oprnegate != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprnegate);
+ ctid | oprnegate 
+------+-----------
+(0 rows)
+
+DROP OPERATOR ===(bigint, bigint);
+CREATE OPERATOR <| (
+        PROCEDURE = int8lt,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+CREATE OPERATOR |> (
+        PROCEDURE = int8gt,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = <|,
+        COMMUTATOR = <|
+);
+DROP OPERATOR |>(bigint, bigint);
+SELECT  ctid, oprcom
+FROM    pg_catalog.pg_operator fk
+WHERE   oprcom != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom);
+ ctid | oprcom 
+------+--------
+(0 rows)
+
+SELECT  ctid, oprnegate
+FROM    pg_catalog.pg_operator fk
+WHERE   oprnegate != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprnegate);
+ ctid | oprnegate 
+------+-----------
+(0 rows)
+
+DROP OPERATOR <|(bigint, bigint);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index bec0316..731677f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -79,7 +79,7 @@ ignore: random
 # ----------
 # Another group of parallel tests
 # ----------
-test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete
+test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete drop_operator
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 7e9b319..4e2fe83 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -115,6 +115,7 @@ test: object_address
 test: tablesample
 test: alter_generic
 test: alter_operator
+test: drop_operator
 test: misc
 test: psql
 test: async
diff --git a/src/test/regress/sql/drop_operator.sql b/src/test/regress/sql/drop_operator.sql
new file mode 100644
index 0000000..cc62cfa
--- /dev/null
+++ b/src/test/regress/sql/drop_operator.sql
@@ -0,0 +1,56 @@
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        COMMUTATOR = ===
+);
+
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===,
+        COMMUTATOR = !==
+);
+
+DROP OPERATOR !==(bigint, bigint);
+
+SELECT  ctid, oprcom
+FROM    pg_catalog.pg_operator fk
+WHERE   oprcom != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom);
+
+SELECT  ctid, oprnegate
+FROM    pg_catalog.pg_operator fk
+WHERE   oprnegate != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprnegate);
+
+DROP OPERATOR ===(bigint, bigint);
+
+CREATE OPERATOR <| (
+        PROCEDURE = int8lt,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+
+CREATE OPERATOR |> (
+        PROCEDURE = int8gt,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = <|,
+        COMMUTATOR = <|
+);
+
+DROP OPERATOR |>(bigint, bigint);
+
+SELECT  ctid, oprcom
+FROM    pg_catalog.pg_operator fk
+WHERE   oprcom != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom);
+
+SELECT  ctid, oprnegate
+FROM    pg_catalog.pg_operator fk
+WHERE   oprnegate != 0 AND
+        NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprnegate);
+
+DROP OPERATOR <|(bigint, bigint);
#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#3)

Robert Haas <robertmhaas@gmail.com> writes:

I did not find this version very clear. It wasn't consistent about
using ObjectIdGetDatum() where needed, but the bigger problem was that
I found the logic unnecessarily convoluted. I rewrote it - I believe
more straightforwardly - as attached. How does this look?

I'd suggest that we save some code by always doing separate updates for
the commutator and negator entries. We can handle the corner case where
they're the same by doing a CommandCounterIncrement between the updates,
instead of having convoluted and probably-never-yet-tested logic.

I'm also a bit dubious of the assumption in RemoveOperatorById that an
operator can't be its own negator. Yeah, that should not be the case,
but if it is the case the deletion will fail outright.

We could resolve both of these issues by changing the semantics of
OprUpdate so that it unconditionally does a CommandCounterIncrement
after each update that it performs. IMO that would be a lot simpler
and more bulletproof; it'd allow removal of a lot of these
overly-tightly-reasoned cases.

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

#5Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#4)

On Wed, Mar 23, 2016 at 12:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

I did not find this version very clear. It wasn't consistent about
using ObjectIdGetDatum() where needed, but the bigger problem was that
I found the logic unnecessarily convoluted. I rewrote it - I believe
more straightforwardly - as attached. How does this look?

I'd suggest that we save some code by always doing separate updates for
the commutator and negator entries. We can handle the corner case where
they're the same by doing a CommandCounterIncrement between the updates,
instead of having convoluted and probably-never-yet-tested logic.

Sure, we could do that, but it isn't necessary. If the logic never
gets hit, the question of whether it has bugs isn't that important.
And I'd rather not rejigger things more than necessary in something
that's going to be back-patched.

I'm also a bit dubious of the assumption in RemoveOperatorById that an
operator can't be its own negator. Yeah, that should not be the case,
but if it is the case the deletion will fail outright.

So what? We've never guaranteed that things are going to work if you
start by corrupting the catalogs, and I wouldn't pick this as a place
to start.

We could resolve both of these issues by changing the semantics of
OprUpdate so that it unconditionally does a CommandCounterIncrement
after each update that it performs. IMO that would be a lot simpler
and more bulletproof; it'd allow removal of a lot of these
overly-tightly-reasoned cases.

I tried this, but it did not seem to work. With the command counter
increments added and the conditional logic removed, I got:

rhaas=# CREATE OPERATOR === (PROCEDURE = int8eq, LEFTARG = bigint,
RIGHTARG = bigint);
CREATE OPERATOR
rhaas=# update pg_operator set oprnegate = oid where oprname = '===';
UPDATE 1
rhaas=# drop operator === (bigint, bigint);
ERROR: attempted to delete invisible tuple

The same test case without those changes fails with:

ERROR: tuple already updated by self

Interestingly, that test case passes on unpatched master.

--
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

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#5)

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Mar 23, 2016 at 12:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I'm also a bit dubious of the assumption in RemoveOperatorById that an
operator can't be its own negator. Yeah, that should not be the case,
but if it is the case the deletion will fail outright.

So what? We've never guaranteed that things are going to work if you
start by corrupting the catalogs, and I wouldn't pick this as a place
to start.

I would not be worried except that it breaks a case that used to work,
as your test below demonstrates.

We could resolve both of these issues by changing the semantics of
OprUpdate so that it unconditionally does a CommandCounterIncrement
after each update that it performs. IMO that would be a lot simpler
and more bulletproof; it'd allow removal of a lot of these
overly-tightly-reasoned cases.

I tried this, but it did not seem to work.

Odd. If you post the revised patch, I'll try to chase down what's wrong.

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

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#6)

I wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Mar 23, 2016 at 12:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

We could resolve both of these issues by changing the semantics of
OprUpdate so that it unconditionally does a CommandCounterIncrement
after each update that it performs. IMO that would be a lot simpler
and more bulletproof; it'd allow removal of a lot of these
overly-tightly-reasoned cases.

I tried this, but it did not seem to work.

Odd. If you post the revised patch, I'll try to chase down what's wrong.

After playing with this, I'll bet you forgot that RemoveOperatorById would
need to re-fetch the target tuple if it got updated. We could
alternatively fix that by skipping updates on the tuple due to be deleted,
but that would convolute the logic in OperatorUpd, which didn't seem
worthwhile to me.

I found some other stuff needing fixing (mostly typos in comments) and
also realized that we don't really need to bother with heap_modify_tuple
at all. I pushed it with those fixes.

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

#8Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#7)

On Fri, Mar 25, 2016 at 12:39 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Mar 23, 2016 at 12:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

We could resolve both of these issues by changing the semantics of
OprUpdate so that it unconditionally does a CommandCounterIncrement
after each update that it performs. IMO that would be a lot simpler
and more bulletproof; it'd allow removal of a lot of these
overly-tightly-reasoned cases.

I tried this, but it did not seem to work.

Odd. If you post the revised patch, I'll try to chase down what's wrong.

After playing with this, I'll bet you forgot that RemoveOperatorById would
need to re-fetch the target tuple if it got updated. We could
alternatively fix that by skipping updates on the tuple due to be deleted,
but that would convolute the logic in OperatorUpd, which didn't seem
worthwhile to me.

I found some other stuff needing fixing (mostly typos in comments) and
also realized that we don't really need to bother with heap_modify_tuple
at all. I pushed it with those fixes.

Cool.

--
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