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

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

Hello, hackers!

As was mentioned in that message [1]/messages/by-id/CA+TgmoYThtZZf6yhnq22SZPw7OTiT68iu9wvvidb2Jz50KdAnQ@mail.gmail.com, and earlier in [2]/messages/by-id/15208.1285772173@sss.pgh.pa.us, DROPping OPERATOR with
negator or commutator doesn't update respective fields (oprnegate and oprcom) on
them. While this issue is probably not very frequent, this behaviour leaves
database in inconsistent state and prevents from using former negator or
commutator.

Proposed patch fixes unwanted behaviour by reseting oprnegate and oprcom fields
to InvalidOid when deleting operator. Code updates tries to update other
operators only if necessary, and only if negator or commutator have target oid
in their respective fields.

To implement desired behaviour with as little code as possible, OperatorUpd
function is extended to accept additional parameter and made externally visible.
It's used from RemoveOperatorById, after checking that update is indeed needed.

Regression tests are added to check DROP OPERATOR behaves as intended (including
case with self-commutator and unlikely case with operator being both negator and
commutator).

Should this patch be added to CommitFest?

[1]: /messages/by-id/CA+TgmoYThtZZf6yhnq22SZPw7OTiT68iu9wvvidb2Jz50KdAnQ@mail.gmail.com
[2]: /messages/by-id/15208.1285772173@sss.pgh.pa.us

Cheers,
Roma

Attachments:

fix_drop_operator_reset_oprcom_and_oprnegate_fields_v1.patchapplication/octet-stream; name=fix_drop_operator_reset_oprcom_and_oprnegate_fields_v1.patchDownload
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 3cd1899..d40c814 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;
@@ -667,7 +670,8 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 	 * We need a CommandCounterIncrement here in case of a self-commutator
 	 * operator: we'll need to update the tuple that we just inserted.
 	 */
-	CommandCounterIncrement();
+	if (!isDelete)
+		CommandCounterIncrement();
 
 	pg_operator_desc = heap_open(OperatorRelationId, RowExclusiveLock);
 
@@ -683,18 +687,26 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 		if (HeapTupleIsValid(tup))
 		{
 			Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup);
-
-			if (!OidIsValid(t->oprcom) || !OidIsValid(t->oprnegate))
+			/*
+			 * When deleting, check, whether other op field points to operator
+			 * being updated. Otherwise, check, whether field is not set.
+			 */
+			if (isDelete ? (t->oprcom == baseId || t->oprnegate == baseId)
+				: !OidIsValid(t->oprcom) || !OidIsValid(t->oprnegate))
 			{
-				if (!OidIsValid(t->oprnegate))
+				if (isDelete ? t->oprnegate == baseId : !OidIsValid(t->oprnegate))
 				{
-					values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(baseId);
+					values[Anum_pg_operator_oprnegate - 1] = (isDelete ?
+															  InvalidOid :
+															  ObjectIdGetDatum(baseId));
 					replaces[Anum_pg_operator_oprnegate - 1] = true;
 				}
 
-				if (!OidIsValid(t->oprcom))
+				if (isDelete ? t->oprcom == baseId : !OidIsValid(t->oprcom))
 				{
-					values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(baseId);
+					values[Anum_pg_operator_oprcom - 1] = (isDelete ?
+														   InvalidOid :
+														   ObjectIdGetDatum(baseId));
 					replaces[Anum_pg_operator_oprcom - 1] = true;
 				}
 
@@ -717,45 +729,55 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 
 	/* if commutator and negator are different, do two updates */
 
-	if (HeapTupleIsValid(tup) &&
-		!(OidIsValid(((Form_pg_operator) GETSTRUCT(tup))->oprcom)))
+	if (HeapTupleIsValid(tup))
 	{
-		values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(baseId);
-		replaces[Anum_pg_operator_oprcom - 1] = true;
+		Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup);
+		if (isDelete ? t->oprcom == baseId : !OidIsValid(t->oprcom))
+		{
 
-		tup = heap_modify_tuple(tup,
-								RelationGetDescr(pg_operator_desc),
-								values,
-								nulls,
-								replaces);
+			values[Anum_pg_operator_oprcom - 1] = (isDelete ?
+												   InvalidOid :
+												   ObjectIdGetDatum(baseId));
+			replaces[Anum_pg_operator_oprcom - 1] =true;
 
-		simple_heap_update(pg_operator_desc, &tup->t_self, tup);
+			tup = heap_modify_tuple(tup,
+									RelationGetDescr(pg_operator_desc),
+									values,
+									nulls,
+									replaces);
 
-		CatalogUpdateIndexes(pg_operator_desc, tup);
+			simple_heap_update(pg_operator_desc, &tup->t_self, tup);
 
-		values[Anum_pg_operator_oprcom - 1] = (Datum) NULL;
-		replaces[Anum_pg_operator_oprcom - 1] = false;
+			CatalogUpdateIndexes(pg_operator_desc, tup);
+
+			values[Anum_pg_operator_oprcom - 1] = (Datum) NULL;
+			replaces[Anum_pg_operator_oprcom - 1] = false;
+		}
 	}
 
 	/* check and update the negator, if necessary */
-
 	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(negId));
 
-	if (HeapTupleIsValid(tup) &&
-		!(OidIsValid(((Form_pg_operator) GETSTRUCT(tup))->oprnegate)))
+	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] = (isDelete ?
+													  InvalidOid :
+													  ObjectIdGetDatum(baseId));
+			replaces[Anum_pg_operator_oprnegate - 1] = true;
 
-		tup = heap_modify_tuple(tup,
-								RelationGetDescr(pg_operator_desc),
-								values,
-								nulls,
-								replaces);
+			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);
+		}
 	}
 
 	heap_close(pg_operator_desc, RowExclusiveLock);
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index 664e5d7..b389c24 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -339,15 +339,30 @@ 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.
+	 */
+	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..208ed80
--- /dev/null
+++ b/src/test/regress/expected/drop_operator.out
@@ -0,0 +1,142 @@
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+      objid = '===(bigint,bigint)'::regoperator
+ORDER BY 1;
+      ref      | deptype 
+---------------+---------
+ schema public | n
+(1 row)
+
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===
+);
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op  | negator 
+-----+---------
+ === | !==
+ !== | ===
+(2 rows)
+
+DROP OPERATOR !== (bigint, bigint);
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op | negator 
+----+---------
+(0 rows)
+
+SELECT oprname, oprnegate FROM pg_operator WHERE oprname = '==='
+    AND oprleft = 'bigint'::regtype AND oprright = 'bigint'::regtype;
+ oprname | oprnegate 
+---------+-----------
+ ===     |         0
+(1 row)
+
+DROP OPERATOR ===(bigint,bigint);
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        COMMUTATOR = ===
+);
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===,
+        COMMUTATOR = !==
+);
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op  | negator 
+-----+---------
+ === | !==
+ !== | ===
+(2 rows)
+
+SELECT po.oprname AS op, po2.oprname AS commutator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype
+    ORDER BY po.oprname;
+ op  | commutator 
+-----+------------
+ !== | !==
+ === | ===
+(2 rows)
+
+DROP OPERATOR !==(bigint, bigint);
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op | negator 
+----+---------
+(0 rows)
+
+SELECT po.oprname AS op, po2.oprname AS commutator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op  | commutator 
+-----+------------
+ === | ===
+(1 row)
+
+DROP OPERATOR ===(bigint, bigint);
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===,
+        COMMUTATOR = ===
+);
+SELECT po.oprname AS op, po2.oprname AS commutator, po3.oprname as negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+    JOIN pg_operator AS po3 ON po.oid = po3.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprname = '===' AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op  | commutator | negator 
+-----+------------+---------
+ === | !==        | !==
+(1 row)
+
+DROP OPERATOR !==(bigint, bigint);
+SELECT po.oprname AS op, po2.oprname AS commutator, po3.oprname as negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+    JOIN pg_operator AS po3 ON po.oid = po3.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprname = '===' AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op | commutator | negator 
+----+------------+---------
+(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..05bc383
--- /dev/null
+++ b/src/test/regress/sql/drop_operator.sql
@@ -0,0 +1,113 @@
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+      objid = '===(bigint,bigint)'::regoperator
+ORDER BY 1;
+
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===
+);
+
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+DROP OPERATOR !== (bigint, bigint);
+
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+SELECT oprname, oprnegate FROM pg_operator WHERE oprname = '==='
+    AND oprleft = 'bigint'::regtype AND oprright = 'bigint'::regtype;
+
+DROP OPERATOR ===(bigint,bigint);
+
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        COMMUTATOR = ===
+);
+
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===,
+        COMMUTATOR = !==
+);
+
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+SELECT po.oprname AS op, po2.oprname AS commutator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype
+    ORDER BY po.oprname;
+
+DROP OPERATOR !==(bigint, bigint);
+
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+SELECT po.oprname AS op, po2.oprname AS commutator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+DROP OPERATOR ===(bigint, bigint);
+
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===,
+        COMMUTATOR = ===
+);
+
+SELECT po.oprname AS op, po2.oprname AS commutator, po3.oprname as negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+    JOIN pg_operator AS po3 ON po.oid = po3.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprname = '===' AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+DROP OPERATOR !==(bigint, bigint);
+
+SELECT po.oprname AS op, po2.oprname AS commutator, po3.oprname as negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+    JOIN pg_operator AS po3 ON po.oid = po3.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprname = '===' AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+DROP OPERATOR ===(bigint, bigint);
#2Euler Taveira
euler@timbira.com.br
In reply to: Roma Sokolov (#1)
Re: [PATCH] fix DROP OPERATOR to reset links to itself on commutator and negator

On 26-02-2016 12:46, Roma Sokolov wrote:

Regression tests are added to check DROP OPERATOR behaves as intended (including
case with self-commutator and unlikely case with operator being both negator and
commutator).

I don't think those are mandatory.

Should this patch be added to CommitFest?

Why not?

I didn't test your patch but

+  if (isDelete ? (t->oprcom == baseId || t->oprnegate == baseId)
+          : !OidIsValid(t->oprcom) || !OidIsValid(t->oprnegate))

... is hard to understand. Instead, you could separate the conditional
expression into a variable.

+ if (isDelete ? t->oprnegate == baseId : !OidIsValid(t->oprnegate))

It could be separate into a variable to be readable (or at least deserve
a comment).

(isDelete ? InvalidOid : ObjectIdGetDatum(baseId))

... and this one too. It is used in 4 places in that function.

--
Euler Taveira Timbira - http://www.timbira.com.br/
PostgreSQL: Consultoria, Desenvolvimento, Suporte 24x7 e Treinamento

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

#3Roma Sokolov
sokolov.r.v@gmail.com
In reply to: Euler Taveira (#2)
1 attachment(s)
Re: [PATCH] fix DROP OPERATOR to reset links to itself on commutator and negator

Thanks for comments, Euler!

... is hard to understand. Instead, you could separate the conditional
expression into a variable.

Fixed the patch to be more descriptive and to avoid repeating same
computation over and over again. See v2 of the patch attached.

I don't think those are mandatory.

Why do you think that? Should I remove them or maybe send as separate
patch?

Cheers,
Roma

Attachments:

fix_drop_operator_reset_oprcom_and_oprnegate_fields_v2.patchapplication/octet-stream; name=fix_drop_operator_reset_oprcom_and_oprnegate_fields_v2.patchDownload
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 3cd1899..ab969a4 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,7 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 	bool		nulls[Natts_pg_operator];
 	bool		replaces[Natts_pg_operator];
 	Datum		values[Natts_pg_operator];
+	Datum		target;
 
 	for (i = 0; i < Natts_pg_operator; ++i)
 	{
@@ -667,10 +671,16 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 	 * We need a CommandCounterIncrement here in case of a self-commutator
 	 * operator: we'll need to update the tuple that we just inserted.
 	 */
-	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));
 
 	/*
@@ -683,18 +693,27 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 		if (HeapTupleIsValid(tup))
 		{
 			Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup);
-
-			if (!OidIsValid(t->oprcom) || !OidIsValid(t->oprnegate))
+			/*
+			 * When deleting, check, whether other op field points to operator
+			 * being updated. Otherwise, check, whether field is not set.
+			 */
+			bool shouldUpdateNegator = (isDelete ?
+										t->oprnegate == baseId :
+										!OidIsValid(t->oprnegate));
+			bool shouldUpdateCommutator = (isDelete ?
+										   t->oprcom == baseId :
+										   !OidIsValid(t->oprcom));
+			if (shouldUpdateNegator || shouldUpdateCommutator)
 			{
-				if (!OidIsValid(t->oprnegate))
+				if (shouldUpdateNegator)
 				{
-					values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(baseId);
+					values[Anum_pg_operator_oprnegate - 1] = target;
 					replaces[Anum_pg_operator_oprnegate - 1] = true;
 				}
 
-				if (!OidIsValid(t->oprcom))
+				if (shouldUpdateCommutator)
 				{
-					values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(baseId);
+					values[Anum_pg_operator_oprcom - 1] = target;
 					replaces[Anum_pg_operator_oprcom - 1] = true;
 				}
 
@@ -717,45 +736,52 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 
 	/* if commutator and negator are different, do two updates */
 
-	if (HeapTupleIsValid(tup) &&
-		!(OidIsValid(((Form_pg_operator) GETSTRUCT(tup))->oprcom)))
+	if (HeapTupleIsValid(tup))
 	{
-		values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(baseId);
-		replaces[Anum_pg_operator_oprcom - 1] = true;
+		Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup);
+		/* As above, check whether other operator should be updated */
+		if (isDelete ? t->oprcom == baseId : !OidIsValid(t->oprcom))
+		{
 
-		tup = heap_modify_tuple(tup,
-								RelationGetDescr(pg_operator_desc),
-								values,
-								nulls,
-								replaces);
+			values[Anum_pg_operator_oprcom - 1] = target;
+			replaces[Anum_pg_operator_oprcom - 1] =true;
 
-		simple_heap_update(pg_operator_desc, &tup->t_self, tup);
+			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);
+			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;
+		}
 	}
 
 	/* check and update the negator, if necessary */
-
 	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(negId));
 
-	if (HeapTupleIsValid(tup) &&
-		!(OidIsValid(((Form_pg_operator) GETSTRUCT(tup))->oprnegate)))
+	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;
 
-		tup = heap_modify_tuple(tup,
-								RelationGetDescr(pg_operator_desc),
-								values,
-								nulls,
-								replaces);
+			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);
+		}
 	}
 
 	heap_close(pg_operator_desc, RowExclusiveLock);
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index 664e5d7..b389c24 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -339,15 +339,30 @@ 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.
+	 */
+	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..208ed80
--- /dev/null
+++ b/src/test/regress/expected/drop_operator.out
@@ -0,0 +1,142 @@
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+      objid = '===(bigint,bigint)'::regoperator
+ORDER BY 1;
+      ref      | deptype 
+---------------+---------
+ schema public | n
+(1 row)
+
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===
+);
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op  | negator 
+-----+---------
+ === | !==
+ !== | ===
+(2 rows)
+
+DROP OPERATOR !== (bigint, bigint);
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op | negator 
+----+---------
+(0 rows)
+
+SELECT oprname, oprnegate FROM pg_operator WHERE oprname = '==='
+    AND oprleft = 'bigint'::regtype AND oprright = 'bigint'::regtype;
+ oprname | oprnegate 
+---------+-----------
+ ===     |         0
+(1 row)
+
+DROP OPERATOR ===(bigint,bigint);
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        COMMUTATOR = ===
+);
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===,
+        COMMUTATOR = !==
+);
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op  | negator 
+-----+---------
+ === | !==
+ !== | ===
+(2 rows)
+
+SELECT po.oprname AS op, po2.oprname AS commutator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype
+    ORDER BY po.oprname;
+ op  | commutator 
+-----+------------
+ !== | !==
+ === | ===
+(2 rows)
+
+DROP OPERATOR !==(bigint, bigint);
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op | negator 
+----+---------
+(0 rows)
+
+SELECT po.oprname AS op, po2.oprname AS commutator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op  | commutator 
+-----+------------
+ === | ===
+(1 row)
+
+DROP OPERATOR ===(bigint, bigint);
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===,
+        COMMUTATOR = ===
+);
+SELECT po.oprname AS op, po2.oprname AS commutator, po3.oprname as negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+    JOIN pg_operator AS po3 ON po.oid = po3.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprname = '===' AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op  | commutator | negator 
+-----+------------+---------
+ === | !==        | !==
+(1 row)
+
+DROP OPERATOR !==(bigint, bigint);
+SELECT po.oprname AS op, po2.oprname AS commutator, po3.oprname as negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+    JOIN pg_operator AS po3 ON po.oid = po3.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprname = '===' AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+ op | commutator | negator 
+----+------------+---------
+(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..05bc383
--- /dev/null
+++ b/src/test/regress/sql/drop_operator.sql
@@ -0,0 +1,113 @@
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+      objid = '===(bigint,bigint)'::regoperator
+ORDER BY 1;
+
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===
+);
+
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+DROP OPERATOR !== (bigint, bigint);
+
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+SELECT oprname, oprnegate FROM pg_operator WHERE oprname = '==='
+    AND oprleft = 'bigint'::regtype AND oprright = 'bigint'::regtype;
+
+DROP OPERATOR ===(bigint,bigint);
+
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        COMMUTATOR = ===
+);
+
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===,
+        COMMUTATOR = !==
+);
+
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+SELECT po.oprname AS op, po2.oprname AS commutator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype
+    ORDER BY po.oprname;
+
+DROP OPERATOR !==(bigint, bigint);
+
+SELECT po.oprname AS op, po2.oprname AS negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+SELECT po.oprname AS op, po2.oprname AS commutator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+DROP OPERATOR ===(bigint, bigint);
+
+CREATE OPERATOR === (
+        PROCEDURE = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+
+CREATE OPERATOR !== (
+        PROCEDURE = int8ne,
+        LEFTARG = bigint,
+        RIGHTARG = bigint,
+        NEGATOR = ===,
+        COMMUTATOR = ===
+);
+
+SELECT po.oprname AS op, po2.oprname AS commutator, po3.oprname as negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+    JOIN pg_operator AS po3 ON po.oid = po3.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprname = '===' AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+DROP OPERATOR !==(bigint, bigint);
+
+SELECT po.oprname AS op, po2.oprname AS commutator, po3.oprname as negator FROM pg_operator AS po
+    JOIN pg_operator AS po2 ON po.oid = po2.oprcom
+    JOIN pg_operator AS po3 ON po.oid = po3.oprnegate
+WHERE po.oprnamespace = (SELECT oid FROM
+        pg_namespace WHERE nspname = 'public')
+    AND po.oprname = '===' AND po.oprleft = 'bigint'::regtype AND po.oprright = 'bigint'::regtype;
+
+DROP OPERATOR ===(bigint, bigint);
#4Euler Taveira
euler@timbira.com.br
In reply to: Roma Sokolov (#3)
Re: [PATCH] fix DROP OPERATOR to reset links to itself on commutator and negator

On 26-02-2016 17:51, Roma Sokolov wrote:

Fixed the patch to be more descriptive and to avoid repeating same
computation over and over again. See v2 of the patch attached.

Oh, much better.

Why do you think that? Should I remove them or maybe send as separate
patch?

Because it is not a common practice to test catalog dependency on
separate tests (AFAICS initial catalogs are tested with oidjoins.sql).
Also, your test case is too huge for such a small use case. If you can
reduce it to a small set of commands using some of the oidjoins.sql
queries, I think it could be sufficient.

--
Euler Taveira Timbira - http://www.timbira.com.br/
PostgreSQL: Consultoria, Desenvolvimento, Suporte 24x7 e Treinamento

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

#5Michael Paquier
michael.paquier@gmail.com
In reply to: Roma Sokolov (#1)
Re: [PATCH] fix DROP OPERATOR to reset links to itself on commutator and negator

On Sat, Feb 27, 2016 at 12:46 AM, Roma Sokolov <sokolov.r.v@gmail.com> wrote:

Should this patch be added to CommitFest?

Yes please.
--
Michael

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

#6Yury Zhuravlev
u.zhuravlev@postgrespro.ru
In reply to: Roma Sokolov (#3)
Re: [PATCH] fix DROP OPERATOR to reset links to itself on commutator and negator

Roma Sokolov wrote:

See v2 of the patch attached.

Hello.
I have a stylistic comments. Sometimes you forget a space:
+ replaces[Anum_pg_operator_oprcom - 1] =true;

or use tab insted space:
+	if (OidIsValid(op->oprnegate) ||
+		(OidIsValid(op->oprcom)	&& operOid != op->oprcom))
+		OperatorUpd(operOid,
+					operOid	== op->oprcom ?	InvalidOid : op->oprcom,
+					op->oprnegate,
+					true);

And I think if you make this logic into a separate function,
it is possible to simplify the code. OperatorUpd function is too complex.

Also better to add comments to the tests.
The rest seems good.

PS I here thought it would be possible to print operators that have been
changed?

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

#7Roma Sokolov
sokolov.r.v@gmail.com
In reply to: Euler Taveira (#4)
1 attachment(s)
Re: [PATCH] fix DROP OPERATOR to reset links to itself on commutator and negator

On 27 Feb 2016, at 03:46, Euler Taveira <euler@timbira.com.br> wrote:
Because it is not a common practice to test catalog dependency on
separate tests (AFAICS initial catalogs are tested with oidjoins.sql).
Also, your test case is too huge for such a small use case.

It seems oidjoins.sql is automatically generated and contains tests only for
initial database state. On the other hand, there are tests for CREATE OPERATOR
and ALTER OPERATOR, so it seems reasonable to me to have separate DROP OPERATOR
test, or to move all operator related testing to one file. This is however
clearly outside of the scope of this patch, so in v3 I've simplified tests using
queries from oidjoins.sql.

Cheers,
Roma

Attachments:

fix_drop_operator_reset_oprcom_and_oprnegate_fields_v3.patchapplication/octet-stream; name=fix_drop_operator_reset_oprcom_and_oprnegate_fields_v3.patchDownload
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 3cd1899..ab969a4 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,7 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 	bool		nulls[Natts_pg_operator];
 	bool		replaces[Natts_pg_operator];
 	Datum		values[Natts_pg_operator];
+	Datum		target;
 
 	for (i = 0; i < Natts_pg_operator; ++i)
 	{
@@ -667,10 +671,16 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 	 * We need a CommandCounterIncrement here in case of a self-commutator
 	 * operator: we'll need to update the tuple that we just inserted.
 	 */
-	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));
 
 	/*
@@ -683,18 +693,27 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 		if (HeapTupleIsValid(tup))
 		{
 			Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup);
-
-			if (!OidIsValid(t->oprcom) || !OidIsValid(t->oprnegate))
+			/*
+			 * When deleting, check, whether other op field points to operator
+			 * being updated. Otherwise, check, whether field is not set.
+			 */
+			bool shouldUpdateNegator = (isDelete ?
+										t->oprnegate == baseId :
+										!OidIsValid(t->oprnegate));
+			bool shouldUpdateCommutator = (isDelete ?
+										   t->oprcom == baseId :
+										   !OidIsValid(t->oprcom));
+			if (shouldUpdateNegator || shouldUpdateCommutator)
 			{
-				if (!OidIsValid(t->oprnegate))
+				if (shouldUpdateNegator)
 				{
-					values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(baseId);
+					values[Anum_pg_operator_oprnegate - 1] = target;
 					replaces[Anum_pg_operator_oprnegate - 1] = true;
 				}
 
-				if (!OidIsValid(t->oprcom))
+				if (shouldUpdateCommutator)
 				{
-					values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(baseId);
+					values[Anum_pg_operator_oprcom - 1] = target;
 					replaces[Anum_pg_operator_oprcom - 1] = true;
 				}
 
@@ -717,45 +736,52 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 
 	/* if commutator and negator are different, do two updates */
 
-	if (HeapTupleIsValid(tup) &&
-		!(OidIsValid(((Form_pg_operator) GETSTRUCT(tup))->oprcom)))
+	if (HeapTupleIsValid(tup))
 	{
-		values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(baseId);
-		replaces[Anum_pg_operator_oprcom - 1] = true;
+		Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup);
+		/* As above, check whether other operator should be updated */
+		if (isDelete ? t->oprcom == baseId : !OidIsValid(t->oprcom))
+		{
 
-		tup = heap_modify_tuple(tup,
-								RelationGetDescr(pg_operator_desc),
-								values,
-								nulls,
-								replaces);
+			values[Anum_pg_operator_oprcom - 1] = target;
+			replaces[Anum_pg_operator_oprcom - 1] =true;
 
-		simple_heap_update(pg_operator_desc, &tup->t_self, tup);
+			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);
+			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;
+		}
 	}
 
 	/* check and update the negator, if necessary */
-
 	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(negId));
 
-	if (HeapTupleIsValid(tup) &&
-		!(OidIsValid(((Form_pg_operator) GETSTRUCT(tup))->oprnegate)))
+	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;
 
-		tup = heap_modify_tuple(tup,
-								RelationGetDescr(pg_operator_desc),
-								values,
-								nulls,
-								replaces);
+			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);
+		}
 	}
 
 	heap_close(pg_operator_desc, RowExclusiveLock);
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index 664e5d7..b389c24 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -339,15 +339,30 @@ 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.
+	 */
+	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..b6d322f
--- /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 = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+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);
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..435a3da
--- /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 = int8eq,
+        LEFTARG = bigint,
+        RIGHTARG = bigint
+);
+
+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);
#8David Steele
david@pgmasters.net
In reply to: Roma Sokolov (#7)
Re: [PATCH] fix DROP OPERATOR to reset links to itself on commutator and negator

Hi Kevin,

On 3/1/16 11:08 AM, Roma Sokolov wrote:

On 27 Feb 2016, at 03:46, Euler Taveira <euler@timbira.com.br> wrote:
Because it is not a common practice to test catalog dependency on
separate tests (AFAICS initial catalogs are tested with oidjoins.sql).
Also, your test case is too huge for such a small use case.

It seems oidjoins.sql is automatically generated and contains tests only for
initial database state. On the other hand, there are tests for CREATE OPERATOR
and ALTER OPERATOR, so it seems reasonable to me to have separate DROP OPERATOR
test, or to move all operator related testing to one file. This is however
clearly outside of the scope of this patch, so in v3 I've simplified tests using
queries from oidjoins.sql.

You've signed up to review this patch, do you have an idea of when you
might be able to do the review?

Thanks,
--
-David
david@pgmasters.net

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

#9Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Roma Sokolov (#7)
Re: [PATCH] fix DROP OPERATOR to reset links to itself on commutator and negator

Hi,

On 03/01/2016 05:08 PM, Roma Sokolov wrote:

On 27 Feb 2016, at 03:46, Euler Taveira <euler@timbira.com.br> wrote:
Because it is not a common practice to test catalog dependency on
separate tests (AFAICS initial catalogs are tested with oidjoins.sql).
Also, your test case is too huge for such a small use case.

It seems oidjoins.sql is automatically generated and contains tests only for
initial database state. On the other hand, there are tests for CREATE OPERATOR
and ALTER OPERATOR, so it seems reasonable to me to have separate DROP OPERATOR
test, or to move all operator related testing to one file. This is however
clearly outside of the scope of this patch, so in v3 I've simplified tests using
queries from oidjoins.sql.

I think it's OK to have a separate regression test for this. Clearly
oidjoins is not a good place for such tests, and as you point out there
are already tests for CREATE/ALTER OPERATOR, so it's perfectly natural
to add a new file for DROP OPERATOR. I don't think it contradicts any
common practice, as the existing CREATE/ALTER OPERATOR tests do pretty
much the same thing.

One comment for the tests, though - you're using a mix of tabs and
spaces for indentation, which breaks psql unpredictably (when debugging
and pasting the commands to psql). Which is a bit annoying.

A few more comments:

1) OperatorUpd (pg_operator.c)
------------------------------

/*
* 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 (!isDelete)
CommandCounterIncrement();

This would really deserve an explanation of why we don't need to
increment the command counter for a delete.

/* When deleting, reset other operator field to InvalidOid, otherwise,
* set it to point to operator being updated
*/

This comment is a bit broken - the first line should be just '/*' and
the second line uses spaces instead of a tabulator.

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? I mean, how likely it
is to have an operator with the same negator and commutator? And how
often we do DROP OPERATOR? Apparently even the original author doubted
this, according to the comment right in front of the block.

2) operatorcmds.c
------------------

/*
* 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.
*/
if (OidIsValid(op->oprnegate) ||
(OidIsValid(op->oprcom) && operOid != op->oprcom))
OperatorUpd(operOid,
operOid == op->oprcom ? InvalidOid : op->oprcom,
op->oprnegate,
true);

Firstly, this block contains tabulators within both the comment and the
code (e.g. "is not" or in front of the "&&" operator. That seems a bit
broken, I guess.

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

if (OidIsValid(op->oprnegate) ||
(OidIsValid(op->oprcom) && operOid != op->oprcom) ||
(OidIsValid(op->oprnegate) && operOid != op->oprnegate))
OperatorUpd(operOid,
operOid == op->oprcom ? InvalidOid : op->oprcom,
operOid == op->oprnegate ? InvalidOid : op->oprnegate,
true);

regards

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