From 5db252461ca95ccf40186667c3fd5f14602384f3 Mon Sep 17 00:00:00 2001
From: Alexander Kuzmenkov <a.kuzmenkov@postgrespro.ru>
Date: Tue, 10 Apr 2018 12:29:47 +0300
Subject: [PATCH 1/2] Preparatory refactoring.

Separate the concepts of "mergejoinable clause" and "equivalence clause". The
former are used to perform merge joins, and the latter -- to build the
equivalence classes. Previously, the only mergejoinable clauses were equality
ones, marked by non-NIL `mergeopfamilies` list. Now that we are going to support
merge joins on inequality clauses, `mergeopfamilies` is set for both equality
and inequality clauses, and in addition to that, the equality clauses have the
flag `is_mj_equality` set to true.

Also rename some things in nodeMergejoin.c to better reflect their purpose.
---
 src/backend/executor/nodeMergejoin.c      | 113 +++++++++++++++++-------------
 src/backend/nodes/copyfuncs.c             |   1 +
 src/backend/nodes/outfuncs.c              |   1 +
 src/backend/optimizer/path/equivclass.c   |  11 +--
 src/backend/optimizer/path/indxpath.c     |   4 +-
 src/backend/optimizer/path/joinrels.c     |   2 +-
 src/backend/optimizer/path/pathkeys.c     |   2 +-
 src/backend/optimizer/plan/analyzejoins.c |  28 ++++----
 src/backend/optimizer/plan/initsplan.c    |  18 +++--
 src/backend/optimizer/util/restrictinfo.c |   1 +
 src/backend/utils/cache/lsyscache.c       |  20 +++---
 src/include/executor/execdebug.h          |   2 +-
 src/include/nodes/relation.h              |  10 ++-
 src/include/utils/lsyscache.h             |   4 +-
 14 files changed, 123 insertions(+), 94 deletions(-)

diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 5e52b90..7298e1c 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -41,7 +41,7 @@
  *
  *		Therefore, rather than directly executing the merge join clauses,
  *		we evaluate the left and right key expressions separately and then
- *		compare the columns one at a time (see MJCompare).  The planner
+ *		compare the columns one at a time (see MJTestTuples).  The planner
  *		passes us enough information about the sort ordering of the inputs
  *		to allow us to determine how to make the comparison.  We may use the
  *		appropriate btree comparison function, since Postgres' only notion
@@ -172,31 +172,31 @@ typedef enum
  * to the opfamily and collation, with nulls at the indicated end of the range.
  * This allows us to obtain the needed comparison function from the opfamily.
  */
-static MergeJoinClause
+static void
 MJExamineQuals(List *mergeclauses,
 			   Oid *mergefamilies,
 			   Oid *mergecollations,
 			   int *mergestrategies,
 			   bool *mergenullsfirst,
-			   PlanState *parent)
+			   MergeJoinState *parent)
 {
-	MergeJoinClause clauses;
 	int			nClauses = list_length(mergeclauses);
 	int			iClause;
 	ListCell   *cl;
 
-	clauses = (MergeJoinClause) palloc0(nClauses * sizeof(MergeJoinClauseData));
+	parent->mj_Clauses = (MergeJoinClause)
+		palloc0(nClauses * sizeof(MergeJoinClauseData));
 
 	iClause = 0;
 	foreach(cl, mergeclauses)
 	{
 		OpExpr	   *qual = (OpExpr *) lfirst(cl);
-		MergeJoinClause clause = &clauses[iClause];
+		MergeJoinClause clause = &parent->mj_Clauses[iClause];
 		Oid			opfamily = mergefamilies[iClause];
 		Oid			collation = mergecollations[iClause];
-		StrategyNumber opstrategy = mergestrategies[iClause];
+		StrategyNumber sort_strategy = mergestrategies[iClause];
 		bool		nulls_first = mergenullsfirst[iClause];
-		int			op_strategy;
+		int			join_strategy;
 		Oid			op_lefttype;
 		Oid			op_righttype;
 		Oid			sortfunc;
@@ -207,26 +207,28 @@ MJExamineQuals(List *mergeclauses,
 		/*
 		 * Prepare the input expressions for execution.
 		 */
-		clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent);
-		clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent);
+		clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args),
+									 (PlanState *) parent);
+		clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args),
+									 (PlanState *) parent);
 
 		/* Set up sort support data */
 		clause->ssup.ssup_cxt = CurrentMemoryContext;
 		clause->ssup.ssup_collation = collation;
-		if (opstrategy == BTLessStrategyNumber)
+		if (sort_strategy == BTLessStrategyNumber)
 			clause->ssup.ssup_reverse = false;
-		else if (opstrategy == BTGreaterStrategyNumber)
+		else if (sort_strategy == BTGreaterStrategyNumber)
 			clause->ssup.ssup_reverse = true;
 		else					/* planner screwed up */
-			elog(ERROR, "unsupported mergejoin strategy %d", opstrategy);
+			elog(ERROR, "unsupported mergejoin strategy %d", sort_strategy);
 		clause->ssup.ssup_nulls_first = nulls_first;
 
 		/* Extract the operator's declared left/right datatypes */
 		get_op_opfamily_properties(qual->opno, opfamily, false,
-								   &op_strategy,
+								   &join_strategy,
 								   &op_lefttype,
 								   &op_righttype);
-		if (op_strategy != BTEqualStrategyNumber)	/* should not happen */
+		if (join_strategy != BTEqualStrategyNumber)	/* should not happen */
 			elog(ERROR, "cannot merge using non-equality operator %u",
 				 qual->opno);
 
@@ -265,8 +267,6 @@ MJExamineQuals(List *mergeclauses,
 
 		iClause++;
 	}
-
-	return clauses;
 }
 
 /*
@@ -378,20 +378,27 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
 	return result;
 }
 
+/* Tuple test result */
+typedef enum
+{
+	MJCR_NextInner,
+	MJCR_NextOuter,
+	MJCR_Join
+} MJTestResult;
+
 /*
- * MJCompare
+ * MJTestTuples
  *
- * Compare the mergejoinable values of the current two input tuples
- * and return 0 if they are equal (ie, the mergejoin equalities all
- * succeed), >0 if outer > inner, <0 if outer < inner.
+ * Decide whether to join current inner and outer tuples, or to advance
+ * either pointer.
  *
  * MJEvalOuterValues and MJEvalInnerValues must already have been called
  * for the current outer and inner tuples, respectively.
  */
-static int
-MJCompare(MergeJoinState *mergestate)
+static MJTestResult
+MJTestTuples(MergeJoinState *mergestate)
 {
-	int			result = 0;
+	MJTestResult result = MJCR_Join;
 	bool		nulleqnull = false;
 	ExprContext *econtext = mergestate->js.ps.ps_ExprContext;
 	int			i;
@@ -408,6 +415,7 @@ MJCompare(MergeJoinState *mergestate)
 	for (i = 0; i < mergestate->mj_NumClauses; i++)
 	{
 		MergeJoinClause clause = &mergestate->mj_Clauses[i];
+		int			sort_result;
 
 		/*
 		 * Special case for NULL-vs-NULL, else use standard comparison.
@@ -418,11 +426,14 @@ MJCompare(MergeJoinState *mergestate)
 			continue;
 		}
 
-		result = ApplySortComparator(clause->ldatum, clause->lisnull,
-									 clause->rdatum, clause->risnull,
-									 &clause->ssup);
+		sort_result = ApplySortComparator(clause->ldatum, clause->lisnull,
+										  clause->rdatum, clause->risnull,
+										  &clause->ssup);
+
+		result = sort_result == 0 ? MJCR_Join
+					: sort_result < 0 ? MJCR_NextOuter : MJCR_NextInner;
 
-		if (result != 0)
+		if (result != MJCR_Join)
 			break;
 	}
 
@@ -435,9 +446,9 @@ MJCompare(MergeJoinState *mergestate)
 	 * equality.  We have to check this as part of the mergequals, else the
 	 * rescan logic will do the wrong thing.
 	 */
-	if (result == 0 &&
+	if (result == MJCR_Join &&
 		(nulleqnull || mergestate->mj_ConstFalseJoin))
-		result = 1;
+		result = MJCR_NextInner;
 
 	MemoryContextSwitchTo(oldContext);
 
@@ -603,7 +614,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprState  *joinqual;
 	ExprState  *otherqual;
 	bool		qualResult;
-	int			compareResult;
+	MJTestResult testResult;
 	PlanState  *innerPlan;
 	TupleTableSlot *innerTupleSlot;
 	PlanState  *outerPlan;
@@ -888,14 +899,14 @@ ExecMergeJoin(PlanState *pstate)
 						 * If they do not match then advance to next outer
 						 * tuple.
 						 */
-						compareResult = MJCompare(node);
-						MJ_DEBUG_COMPARE(compareResult);
+						testResult = MJTestTuples(node);
+						MJ_DEBUG_COMPARE(testResult);
 
-						if (compareResult == 0)
+						if (testResult == MJCR_Join)
 							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
 						else
 						{
-							Assert(compareResult < 0);
+							Assert(testResult == MJCR_NextOuter);
 							node->mj_JoinState = EXEC_MJ_NEXTOUTER;
 						}
 						break;
@@ -1045,10 +1056,10 @@ ExecMergeJoin(PlanState *pstate)
 				innerTupleSlot = node->mj_MarkedTupleSlot;
 				(void) MJEvalInnerValues(node, innerTupleSlot);
 
-				compareResult = MJCompare(node);
-				MJ_DEBUG_COMPARE(compareResult);
+				testResult = MJTestTuples(node);
+				MJ_DEBUG_COMPARE(testResult);
 
-				if (compareResult == 0)
+				if (testResult == MJCR_Join)
 				{
 					/*
 					 * the merge clause matched so now we restore the inner
@@ -1106,7 +1117,7 @@ ExecMergeJoin(PlanState *pstate)
 					 *	no more inners, no more matches are possible.
 					 * ----------------
 					 */
-					Assert(compareResult > 0);
+					Assert(testResult == MJCR_NextInner);
 					innerTupleSlot = node->mj_InnerTupleSlot;
 
 					/* reload comparison data for current inner */
@@ -1179,10 +1190,10 @@ ExecMergeJoin(PlanState *pstate)
 				 * satisfy the mergeclauses.  If they do, then we update the
 				 * marked tuple position and go join them.
 				 */
-				compareResult = MJCompare(node);
-				MJ_DEBUG_COMPARE(compareResult);
+				testResult = MJTestTuples(node);
+				MJ_DEBUG_COMPARE(testResult);
 
-				if (compareResult == 0)
+				if (testResult == MJCR_Join)
 				{
 					if (!node->mj_SkipMarkRestore)
 						ExecMarkPos(innerPlan);
@@ -1191,11 +1202,13 @@ ExecMergeJoin(PlanState *pstate)
 
 					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
 				}
-				else if (compareResult < 0)
+				else if (testResult == MJCR_NextOuter)
 					node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
 				else
-					/* compareResult > 0 */
+				{
+					Assert(testResult == MJCR_NextInner);
 					node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+				}
 				break;
 
 				/*
@@ -1593,12 +1606,12 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 	 * preprocess the merge clauses
 	 */
 	mergestate->mj_NumClauses = list_length(node->mergeclauses);
-	mergestate->mj_Clauses = MJExamineQuals(node->mergeclauses,
-											node->mergeFamilies,
-											node->mergeCollations,
-											node->mergeStrategies,
-											node->mergeNullsFirst,
-											(PlanState *) mergestate);
+	MJExamineQuals(node->mergeclauses,
+				   node->mergeFamilies,
+				   node->mergeCollations,
+				   node->mergeStrategies,
+				   node->mergeNullsFirst,
+				   mergestate);
 
 	/*
 	 * initialize join state
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 17b650b..54687f0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2240,6 +2240,7 @@ _copyRestrictInfo(const RestrictInfo *from)
 	COPY_SCALAR_FIELD(norm_selec);
 	COPY_SCALAR_FIELD(outer_selec);
 	COPY_NODE_FIELD(mergeopfamilies);
+	COPY_SCALAR_FIELD(is_mj_equality);
 	/* EquivalenceClasses are never copied, so shallow-copy the pointers */
 	COPY_SCALAR_FIELD(left_ec);
 	COPY_SCALAR_FIELD(right_ec);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index a6454ce..da2a627 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2535,6 +2535,7 @@ _outRestrictInfo(StringInfo str, const RestrictInfo *node)
 	WRITE_FLOAT_FIELD(norm_selec, "%.4f");
 	WRITE_FLOAT_FIELD(outer_selec, "%.4f");
 	WRITE_NODE_FIELD(mergeopfamilies);
+	WRITE_BOOL_FIELD(is_mj_equality);
 	/* don't write left_ec, leads to infinite recursion in plan tree dump */
 	/* don't write right_ec, leads to infinite recursion in plan tree dump */
 	WRITE_NODE_FIELD(left_em);
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index b22b36e..75c8074 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -68,9 +68,9 @@ static bool reconsider_full_join_clause(PlannerInfo *root,
 
 /*
  * process_equivalence
- *	  The given clause has a mergejoinable operator and can be applied without
- *	  any delay by an outer join, so its two sides can be considered equal
- *	  anywhere they are both computable; moreover that equality can be
+ *	  The given clause has a mergejoinable equality operator and can be applied
+ * 	  without any delay by an outer join, so its two sides can be considered
+ * 	  equal anywhere they are both computable; moreover that equality can be
  *	  extended transitively.  Record this knowledge in the EquivalenceClass
  *	  data structure, if applicable.  Returns true if successful, false if not
  *	  (in which case caller should treat the clause as ordinary, not an
@@ -233,6 +233,7 @@ process_equivalence(PlannerInfo *root,
 	op_input_types(opno, &item1_type, &item2_type);
 
 	opfamilies = restrictinfo->mergeopfamilies;
+	Assert(restrictinfo->is_mj_equality);
 
 	/*
 	 * Sweep through the existing EquivalenceClasses looking for matches to
@@ -273,7 +274,7 @@ process_equivalence(PlannerInfo *root,
 		/*
 		 * A "match" requires matching sets of btree opfamilies.  Use of
 		 * equal() for this test has implications discussed in the comments
-		 * for get_mergejoin_opfamilies().
+		 * for get_btree_equality_opfamilies().
 		 */
 		if (!equal(opfamilies, cur_ec->ec_opfamilies))
 			continue;
@@ -2082,7 +2083,7 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root,
 				 * to test for member matches first.
 				 */
 				if (opfamilies == NIL)	/* compute if we didn't already */
-					opfamilies = get_mergejoin_opfamilies(eqop);
+					opfamilies = get_btree_equality_opfamilies(eqop);
 				if (equal(opfamilies, ec->ec_opfamilies))
 					return ec;
 				/* Otherwise, done with this EC, move on to the next */
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index f295558..3e05b45 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -3008,8 +3008,8 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
 		 * mergeopfamilies will be if it has a mergejoinable operator and
 		 * doesn't contain volatile functions.
 		 */
-		if (restrictinfo->mergeopfamilies == NIL)
-			continue;			/* not mergejoinable */
+		if (!restrictinfo->is_mj_equality)
+			continue;			/* not a mergejoinable equality */
 
 		/*
 		 * The clause certainly doesn't refer to anything but the given rel.
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 7008e13..dc9f4c3 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -1452,7 +1452,7 @@ have_partkey_equi_join(RelOptInfo *joinrel,
 			continue;
 
 		/* Skip clauses which are not equality conditions. */
-		if (!rinfo->mergeopfamilies && !OidIsValid(rinfo->hashjoinoperator))
+		if (!rinfo->is_mj_equality && !OidIsValid(rinfo->hashjoinoperator))
 			continue;
 
 		opexpr = (OpExpr *) rinfo->clause;
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ec66cb9..0fa6f91 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -199,7 +199,7 @@ make_pathkey_from_sortinfo(PlannerInfo *root,
 	if (!OidIsValid(equality_op))	/* shouldn't happen */
 		elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
 			 BTEqualStrategyNumber, opcintype, opcintype, opfamily);
-	opfamilies = get_mergejoin_opfamilies(equality_op);
+	opfamilies = get_btree_equality_opfamilies(equality_op);
 	if (!opfamilies)			/* certainly should find some */
 		elog(ERROR, "could not find opfamilies for equality operator %u",
 			 equality_op);
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 0e73f9c..1fcfb2c 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -238,11 +238,10 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
 	}
 
 	/*
-	 * Search for mergejoinable clauses that constrain the inner rel against
-	 * either the outer rel or a pseudoconstant.  If an operator is
-	 * mergejoinable then it behaves like equality for some btree opclass, so
-	 * it's what we want.  The mergejoinability test also eliminates clauses
-	 * containing volatile functions, which we couldn't depend on.
+	 * Search for mergejoinable equality clauses that constrain the inner rel
+	 * against either the outer rel or a pseudoconstant. Mergejoinable equality
+	 * clauses are based on equality operators for some btree opclass, and don't
+	 * contain volatile functions, so it's what we want.
 	 */
 	foreach(l, innerrel->joininfo)
 	{
@@ -267,10 +266,10 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
 			continue;			/* else, ignore; not useful here */
 		}
 
-		/* Ignore if it's not a mergejoinable clause */
+		/* Ignore if it's not a mergejoinable equality clause */
 		if (!restrictinfo->can_join ||
-			restrictinfo->mergeopfamilies == NIL)
-			continue;			/* not mergejoinable */
+			!restrictinfo->is_mj_equality)
+			continue;
 
 		/*
 		 * Check if clause has the form "outer op inner" or "inner op outer",
@@ -1087,11 +1086,10 @@ is_innerrel_unique_for(PlannerInfo *root,
 	ListCell   *lc;
 
 	/*
-	 * Search for mergejoinable clauses that constrain the inner rel against
-	 * the outer rel.  If an operator is mergejoinable then it behaves like
-	 * equality for some btree opclass, so it's what we want.  The
-	 * mergejoinability test also eliminates clauses containing volatile
-	 * functions, which we couldn't depend on.
+	 * Search for mergejoinable equality clauses that constrain the inner rel
+	 * against either the outer rel. Mergejoinable equality clauses are based
+	 * on equality operators for some btree opclass, and don't contain volatile
+	 * functions, so it's what we want.
 	 */
 	foreach(lc, restrictlist)
 	{
@@ -1105,9 +1103,9 @@ is_innerrel_unique_for(PlannerInfo *root,
 			RINFO_IS_PUSHED_DOWN(restrictinfo, joinrelids))
 			continue;
 
-		/* Ignore if it's not a mergejoinable clause */
+		/* Ignore if it's not a mergejoinable equality clause */
 		if (!restrictinfo->can_join ||
-			restrictinfo->mergeopfamilies == NIL)
+			!restrictinfo->is_mj_equality)
 			continue;			/* not mergejoinable */
 
 		/*
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 01335db..cc042d4 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -1552,8 +1552,8 @@ compute_semijoin_info(SpecialJoinInfo *sjinfo, List *clause)
 		if (all_btree)
 		{
 			/* oprcanmerge is considered a hint... */
-			if (!op_mergejoinable(opno, opinputtype) ||
-				get_mergejoin_opfamilies(opno) == NIL)
+			if (!op_mergejoinable_equality(opno, opinputtype) ||
+				get_btree_equality_opfamilies(opno) == NIL)
 				all_btree = false;
 		}
 		if (all_hash)
@@ -1964,15 +1964,17 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	 * process_equivalence is successful, it will take care of that;
 	 * otherwise, we have to call initialize_mergeclause_eclasses to do it.
 	 */
-	if (restrictinfo->mergeopfamilies)
+	if (restrictinfo->is_mj_equality)
 	{
+		Assert(restrictinfo->mergeopfamilies != NIL);
+
 		if (maybe_equivalence)
 		{
 			if (check_equivalence_delay(root, restrictinfo) &&
 				process_equivalence(root, &restrictinfo, below_outer_join))
 				return;
 			/* EC rejected it, so set left_ec/right_ec the hard way ... */
-			if (restrictinfo->mergeopfamilies)	/* EC might have changed this */
+			if (restrictinfo->is_mj_equality)	/* EC might have changed this */
 				initialize_mergeclause_eclasses(root, restrictinfo);
 			/* ... and fall through to distribute_restrictinfo_to_rels */
 		}
@@ -2621,9 +2623,13 @@ check_mergejoinable(RestrictInfo *restrictinfo)
 	opno = ((OpExpr *) clause)->opno;
 	leftarg = linitial(((OpExpr *) clause)->args);
 
-	if (op_mergejoinable(opno, exprType(leftarg)) &&
+	if (op_mergejoinable_equality(opno, exprType(leftarg)) &&
 		!contain_volatile_functions((Node *) clause))
-		restrictinfo->mergeopfamilies = get_mergejoin_opfamilies(opno);
+	{
+		restrictinfo->mergeopfamilies = get_btree_equality_opfamilies(opno);
+		if (restrictinfo->mergeopfamilies != NIL)
+			restrictinfo->is_mj_equality = true;
+	}
 
 	/*
 	 * Note: op_mergejoinable is just a hint; if we fail to find the operator
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index edf5a48..a928d6c 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -186,6 +186,7 @@ make_restrictinfo_internal(Expr *clause,
 	restrictinfo->outer_selec = -1;
 
 	restrictinfo->mergeopfamilies = NIL;
+	restrictinfo->is_mj_equality = false;
 
 	restrictinfo->left_ec = NULL;
 	restrictinfo->right_ec = NULL;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index bba595a..a8a175c 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -341,9 +341,9 @@ get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type)
 }
 
 /*
- * get_mergejoin_opfamilies
- *		Given a putatively mergejoinable operator, return a list of the OIDs
- *		of the btree opfamilies in which it represents equality.
+ * get_btree_equality_opfamilies
+ *		Given an operator, return a list of the OIDs of the btree opfamilies
+ * 		in which it represents equality.
  *
  * It is possible (though at present unusual) for an operator to be equality
  * in more than one opfamily, hence the result is a list.  This also lets us
@@ -360,7 +360,7 @@ get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type)
  * or cycles here to guarantee the ordering in that case.
  */
 List *
-get_mergejoin_opfamilies(Oid opno)
+get_btree_equality_opfamilies(Oid opno)
 {
 	List	   *result = NIL;
 	CatCList   *catlist;
@@ -1164,11 +1164,11 @@ op_input_types(Oid opno, Oid *lefttype, Oid *righttype)
 }
 
 /*
- * op_mergejoinable
+ * op_mergejoinable_equality
  *
- * Returns true if the operator is potentially mergejoinable.  (The planner
- * will fail to find any mergejoin plans unless there are suitable btree
- * opfamily entries for this operator and associated sortops.  The pg_operator
+ * Returns true if the operator is a potentially mergejoinable equality operator.
+ * (The planner will fail to find any mergejoin plans unless there are suitable
+ * btree opfamily entries for this operator and associated sortops. The pg_operator
  * flag is just a hint to tell the planner whether to bother looking.)
  *
  * In some cases (currently only array_eq and record_eq), mergejoinability
@@ -1177,7 +1177,7 @@ op_input_types(Oid opno, Oid *lefttype, Oid *righttype)
  * is needed to check this --- by convention, pass the left input's data type.
  */
 bool
-op_mergejoinable(Oid opno, Oid inputtype)
+op_mergejoinable_equality(Oid opno, Oid inputtype)
 {
 	bool		result = false;
 	HeapTuple	tp;
@@ -1234,7 +1234,7 @@ op_hashjoinable(Oid opno, Oid inputtype)
 	HeapTuple	tp;
 	TypeCacheEntry *typentry;
 
-	/* As in op_mergejoinable, let the typcache handle the hard cases */
+	/* As in op_mergejoinable_equality, let the typcache handle the hard cases */
 	/* Eventually we'll need a similar case for record_eq ... */
 	if (opno == ARRAY_EQ_OP)
 	{
diff --git a/src/include/executor/execdebug.h b/src/include/executor/execdebug.h
index 236b2cc..2d5760e 100644
--- a/src/include/executor/execdebug.h
+++ b/src/include/executor/execdebug.h
@@ -105,7 +105,7 @@
 #define MJ_debugtup(slot)				debugtup(slot, NULL)
 #define MJ_dump(state)					ExecMergeTupleDump(state)
 #define MJ_DEBUG_COMPARE(res) \
-  MJ1_printf("  MJCompare() returns %d\n", (res))
+  MJ1_printf("  MJTestTuples() returns %d\n", (res))
 #define MJ_DEBUG_QUAL(clause, res) \
   MJ2_printf("  ExecQual(%s, econtext) returns %s\n", \
 			 CppAsString(clause), T_OR_F(res))
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 41caf87..2e45aee 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1926,8 +1926,16 @@ typedef struct RestrictInfo
 	Selectivity outer_selec;	/* selectivity for outer join semantics; -1 if
 								 * not yet set */
 
-	/* valid if clause is mergejoinable, else NIL */
+	/*
+	 * The following two fields are used for clauses on which it is possible to
+	 * perform a merge join.
+	 * If mergeopfamilies is not NIL, the clause is mergejoinable. Its operator
+	 * may be either equality or inequality in some btree opfamilies. These
+	 * opfamilies are stored in mergeopfamilies, and for equality clauses,
+	 * is_mj_equality is set to true.
+	 */
 	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	bool		is_mj_equality;		/* is this a mergejoinable equality clause? */
 
 	/* cache space for mergeclause processing; NULL if not yet set */
 	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index e55ea40..e8684ad 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -74,7 +74,7 @@ extern bool get_ordering_op_properties(Oid opno,
 						   Oid *opfamily, Oid *opcintype, int16 *strategy);
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
-extern List *get_mergejoin_opfamilies(Oid opno);
+extern List *get_btree_equality_opfamilies(Oid opno);
 extern bool get_compatible_hash_operators(Oid opno,
 							  Oid *lhs_opno, Oid *rhs_opno);
 extern bool get_op_hash_functions(Oid opno,
@@ -99,7 +99,7 @@ extern RegProcedure get_opcode(Oid opno);
 extern char *get_opname(Oid opno);
 extern Oid	get_op_rettype(Oid opno);
 extern void op_input_types(Oid opno, Oid *lefttype, Oid *righttype);
-extern bool op_mergejoinable(Oid opno, Oid inputtype);
+extern bool op_mergejoinable_equality(Oid opno, Oid inputtype);
 extern bool op_hashjoinable(Oid opno, Oid inputtype);
 extern bool op_strict(Oid opno);
 extern char op_volatile(Oid opno);
-- 
2.7.4

