diff --git a/src/backend/executor/execGrouping.c b/src/backend/executor/execGrouping.c
index eed92f6533fe722c3445764d948d81625e4044bb..f84c1120db23683ddcf73644cb2551a1b7c95205 100644
--- a/src/backend/executor/execGrouping.c
+++ b/src/backend/executor/execGrouping.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execGrouping.c,v 1.22 2007/01/05 22:19:27 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execGrouping.c,v 1.23 2007/01/10 18:06:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -183,24 +183,22 @@ execTuplesUnequal(TupleTableSlot *slot1,
  * The result is a palloc'd array.
  */
 FmgrInfo *
-execTuplesMatchPrepare(TupleDesc tupdesc,
-					   int numCols,
-					   AttrNumber *matchColIdx)
+execTuplesMatchPrepare(int numCols,
+					   Oid *eqOperators)
 {
-	FmgrInfo   *eqfunctions = (FmgrInfo *) palloc(numCols * sizeof(FmgrInfo));
+	FmgrInfo   *eqFunctions = (FmgrInfo *) palloc(numCols * sizeof(FmgrInfo));
 	int			i;
 
 	for (i = 0; i < numCols; i++)
 	{
-		AttrNumber	att = matchColIdx[i];
-		Oid			typid = tupdesc->attrs[att - 1]->atttypid;
+		Oid			eq_opr = eqOperators[i];
 		Oid			eq_function;
 
-		eq_function = equality_oper_funcid(typid);
-		fmgr_info(eq_function, &eqfunctions[i]);
+		eq_function = get_opcode(eq_opr);
+		fmgr_info(eq_function, &eqFunctions[i]);
 	}
 
-	return eqfunctions;
+	return eqFunctions;
 }
 
 /*
@@ -208,40 +206,33 @@ execTuplesMatchPrepare(TupleDesc tupdesc,
  *		Look up the equality and hashing functions needed for a TupleHashTable.
  *
  * This is similar to execTuplesMatchPrepare, but we also need to find the
- * hash functions associated with the equality operators.  *eqfunctions and
- * *hashfunctions receive the palloc'd result arrays.
+ * hash functions associated with the equality operators.  *eqFunctions and
+ * *hashFunctions receive the palloc'd result arrays.
  */
 void
-execTuplesHashPrepare(TupleDesc tupdesc,
-					  int numCols,
-					  AttrNumber *matchColIdx,
-					  FmgrInfo **eqfunctions,
-					  FmgrInfo **hashfunctions)
+execTuplesHashPrepare(int numCols,
+					  Oid *eqOperators,
+					  FmgrInfo **eqFunctions,
+					  FmgrInfo **hashFunctions)
 {
 	int			i;
 
-	*eqfunctions = (FmgrInfo *) palloc(numCols * sizeof(FmgrInfo));
-	*hashfunctions = (FmgrInfo *) palloc(numCols * sizeof(FmgrInfo));
+	*eqFunctions = (FmgrInfo *) palloc(numCols * sizeof(FmgrInfo));
+	*hashFunctions = (FmgrInfo *) palloc(numCols * sizeof(FmgrInfo));
 
 	for (i = 0; i < numCols; i++)
 	{
-		AttrNumber	att = matchColIdx[i];
-		Oid			typid = tupdesc->attrs[att - 1]->atttypid;
-		Operator	optup;
-		Oid			eq_opr;
+		Oid			eq_opr = eqOperators[i];
 		Oid			eq_function;
 		Oid			hash_function;
 
-		optup = equality_oper(typid, false);
-		eq_opr = oprid(optup);
-		eq_function = oprfuncid(optup);
-		ReleaseSysCache(optup);
+		eq_function = get_opcode(eq_opr);
 		hash_function = get_op_hash_function(eq_opr);
 		if (!OidIsValid(hash_function)) /* should not happen */
 			elog(ERROR, "could not find hash function for hash operator %u",
 				 eq_opr);
-		fmgr_info(eq_function, &(*eqfunctions)[i]);
-		fmgr_info(hash_function, &(*hashfunctions)[i]);
+		fmgr_info(eq_function, &(*eqFunctions)[i]);
+		fmgr_info(hash_function, &(*hashFunctions)[i]);
 	}
 }
 
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 74a00ac892063c7589c1a654b2aa2fff8d18558f..00fb3b86e7172554d989ae3ae53331761e47bc47 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -61,7 +61,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.148 2007/01/09 02:14:11 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.149 2007/01/10 18:06:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1270,16 +1270,14 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	if (node->numCols > 0)
 	{
 		if (node->aggstrategy == AGG_HASHED)
-			execTuplesHashPrepare(ExecGetScanType(&aggstate->ss),
-								  node->numCols,
-								  node->grpColIdx,
+			execTuplesHashPrepare(node->numCols,
+								  node->grpOperators,
 								  &aggstate->eqfunctions,
 								  &aggstate->hashfunctions);
 		else
 			aggstate->eqfunctions =
-				execTuplesMatchPrepare(ExecGetScanType(&aggstate->ss),
-									   node->numCols,
-									   node->grpColIdx);
+				execTuplesMatchPrepare(node->numCols,
+									   node->grpOperators);
 	}
 
 	/*
@@ -1519,6 +1517,13 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 							&peraggstate->inputtypeLen,
 							&peraggstate->inputtypeByVal);
 
+			/*
+			 * Look up the sorting and comparison operators to use.  XXX it's
+			 * pretty bletcherous to be making this sort of semantic decision
+			 * in the executor.  Probably the parser should decide this and
+			 * record it in the Aggref node ... or at latest, do it in the
+			 * planner.
+			 */
 			eq_function = equality_oper_funcid(inputTypes[0]);
 			fmgr_info(eq_function, &(peraggstate->equalfn));
 			peraggstate->sortOperator = ordering_oper_opid(inputTypes[0]);
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 417adeda25385a62a48392d8ac82ea6403af0b36..da4b2bcdb4972e92174c634277a991fedecbdf92 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -15,7 +15,7 @@
  *	  locate group boundaries.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeGroup.c,v 1.66 2007/01/05 22:19:28 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeGroup.c,v 1.67 2007/01/10 18:06:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -211,9 +211,8 @@ ExecInitGroup(Group *node, EState *estate, int eflags)
 	 * Precompute fmgr lookup data for inner loop
 	 */
 	grpstate->eqfunctions =
-		execTuplesMatchPrepare(ExecGetScanType(&grpstate->ss),
-							   node->numCols,
-							   node->grpColIdx);
+		execTuplesMatchPrepare(node->numCols,
+							   node->grpOperators);
 
 	return grpstate;
 }
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 63e9035f546b8e800bb1c65af6462ed81fb5f5d6..a5f08c28ef36a820d74333899d1e720752cc4d2f 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeMergejoin.c,v 1.84 2007/01/05 22:19:28 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeMergejoin.c,v 1.85 2007/01/10 18:06:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -156,35 +156,36 @@ typedef struct MergeJoinClauseData
  * the two expressions from the original clause.
  *
  * In addition to the expressions themselves, the planner passes the btree
- * opfamily OID and btree strategy number (BTLessStrategyNumber or
- * BTGreaterStrategyNumber) that identify the intended merge semantics for
- * each merge key.  The mergejoinable operator is an equality operator in
- * this opfamily, and the two inputs are guaranteed to be ordered in either
- * increasing or decreasing (respectively) order according to this opfamily.
- * This allows us to obtain the needed comparison functions from the opfamily.
+ * opfamily OID, btree strategy number (BTLessStrategyNumber or
+ * BTGreaterStrategyNumber), and nulls-first flag that identify the intended
+ * merge semantics for each merge key.  The mergejoinable operator is an
+ * equality operator in this opfamily, and the two inputs are guaranteed to be
+ * ordered in either increasing or decreasing (respectively) order according
+ * to this opfamily.  This allows us to obtain the needed comparison functions
+ * from the opfamily.
  */
 static MergeJoinClause
-MJExamineQuals(List *mergeclauses, List *mergefamilies, List *mergestrategies,
+MJExamineQuals(List *mergeclauses,
+			   Oid *mergefamilies,
+			   int *mergestrategies,
+			   bool *mergenullsfirst,
 			   PlanState *parent)
 {
 	MergeJoinClause clauses;
 	int			nClauses = list_length(mergeclauses);
 	int			iClause;
 	ListCell   *cl;
-	ListCell   *cf;
-	ListCell   *cs;
 
 	clauses = (MergeJoinClause) palloc0(nClauses * sizeof(MergeJoinClauseData));
 
 	iClause = 0;
-	cf = list_head(mergefamilies);
-	cs = list_head(mergestrategies);
 	foreach(cl, mergeclauses)
 	{
 		OpExpr	   *qual = (OpExpr *) lfirst(cl);
 		MergeJoinClause clause = &clauses[iClause];
-		Oid			opfamily;
-		StrategyNumber opstrategy;
+		Oid			opfamily = mergefamilies[iClause];
+		StrategyNumber opstrategy = mergestrategies[iClause];
+		bool		nulls_first = mergenullsfirst[iClause];
 		int			op_strategy;
 		Oid			op_lefttype;
 		Oid			op_righttype;
@@ -192,14 +193,10 @@ MJExamineQuals(List *mergeclauses, List *mergefamilies, List *mergestrategies,
 		RegProcedure cmpproc;
 		AclResult	aclresult;
 
-		opfamily = lfirst_oid(cf);
-		cf = lnext(cf);
-		opstrategy = lfirst_int(cs);
-		cs = lnext(cs);
-
 		/* Later we'll support both ascending and descending sort... */
 		Assert(opstrategy == BTLessStrategyNumber);
 		clause->cmpstrategy = MERGEFUNC_CMP;
+		Assert(!nulls_first);
 
 		if (!IsA(qual, OpExpr))
 			elog(ERROR, "mergejoin clause is not an OpExpr");
@@ -1525,8 +1522,9 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 	 */
 	mergestate->mj_NumClauses = list_length(node->mergeclauses);
 	mergestate->mj_Clauses = MJExamineQuals(node->mergeclauses,
-											node->mergefamilies,
-											node->mergestrategies,
+											node->mergeFamilies,
+											node->mergeStrategies,
+											node->mergeNullsFirst,
 											(PlanState *) mergestate);
 
 	/*
diff --git a/src/backend/executor/nodeSetOp.c b/src/backend/executor/nodeSetOp.c
index 6d7c9e3b8b55b99e0ddf8629bcc5037bc24f15a6..761f7fc594769385bf84e48ab53786f516f6e68f 100644
--- a/src/backend/executor/nodeSetOp.c
+++ b/src/backend/executor/nodeSetOp.c
@@ -21,7 +21,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeSetOp.c,v 1.23 2007/01/05 22:19:28 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeSetOp.c,v 1.24 2007/01/10 18:06:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -267,9 +267,8 @@ ExecInitSetOp(SetOp *node, EState *estate, int eflags)
 	 * Precompute fmgr lookup data for inner loop
 	 */
 	setopstate->eqfunctions =
-		execTuplesMatchPrepare(ExecGetResultType(&setopstate->ps),
-							   node->numCols,
-							   node->dupColIdx);
+		execTuplesMatchPrepare(node->numCols,
+							   node->dupOperators);
 
 	return setopstate;
 }
diff --git a/src/backend/executor/nodeUnique.c b/src/backend/executor/nodeUnique.c
index 5e0edfb57ba34fbd31d13948947ace96e52f8663..6d64c3e334b4e4f9b0219974a31d49e40ca678a9 100644
--- a/src/backend/executor/nodeUnique.c
+++ b/src/backend/executor/nodeUnique.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeUnique.c,v 1.54 2007/01/05 22:19:28 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeUnique.c,v 1.55 2007/01/10 18:06:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -159,9 +159,8 @@ ExecInitUnique(Unique *node, EState *estate, int eflags)
 	 * Precompute fmgr lookup data for inner loop
 	 */
 	uniquestate->eqfunctions =
-		execTuplesMatchPrepare(ExecGetResultType(&uniquestate->ps),
-							   node->numCols,
-							   node->uniqColIdx);
+		execTuplesMatchPrepare(node->numCols,
+							   node->uniqOperators);
 
 	return uniquestate;
 }
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 08817e54a911a075cf22beede439557a741342ad..4943bb2029724a791ce8c55f475c5778d901af30 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.360 2007/01/09 02:14:11 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.361 2007/01/10 18:06:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -439,6 +439,7 @@ static MergeJoin *
 _copyMergeJoin(MergeJoin *from)
 {
 	MergeJoin  *newnode = makeNode(MergeJoin);
+	int			numCols;
 
 	/*
 	 * copy node superclass fields
@@ -449,8 +450,10 @@ _copyMergeJoin(MergeJoin *from)
 	 * copy remainder of node
 	 */
 	COPY_NODE_FIELD(mergeclauses);
-	COPY_NODE_FIELD(mergefamilies);
-	COPY_NODE_FIELD(mergestrategies);
+	numCols = list_length(from->mergeclauses);
+	COPY_POINTER_FIELD(mergeFamilies, numCols * sizeof(Oid));
+	COPY_POINTER_FIELD(mergeStrategies, numCols * sizeof(int));
+	COPY_POINTER_FIELD(mergeNullsFirst, numCols * sizeof(bool));
 
 	return newnode;
 }
@@ -528,6 +531,7 @@ _copyGroup(Group *from)
 
 	COPY_SCALAR_FIELD(numCols);
 	COPY_POINTER_FIELD(grpColIdx, from->numCols * sizeof(AttrNumber));
+	COPY_POINTER_FIELD(grpOperators, from->numCols * sizeof(Oid));
 
 	return newnode;
 }
@@ -545,7 +549,10 @@ _copyAgg(Agg *from)
 	COPY_SCALAR_FIELD(aggstrategy);
 	COPY_SCALAR_FIELD(numCols);
 	if (from->numCols > 0)
+	{
 		COPY_POINTER_FIELD(grpColIdx, from->numCols * sizeof(AttrNumber));
+		COPY_POINTER_FIELD(grpOperators, from->numCols * sizeof(Oid));
+	}
 	COPY_SCALAR_FIELD(numGroups);
 
 	return newnode;
@@ -569,6 +576,7 @@ _copyUnique(Unique *from)
 	 */
 	COPY_SCALAR_FIELD(numCols);
 	COPY_POINTER_FIELD(uniqColIdx, from->numCols * sizeof(AttrNumber));
+	COPY_POINTER_FIELD(uniqOperators, from->numCols * sizeof(Oid));
 
 	return newnode;
 }
@@ -612,6 +620,7 @@ _copySetOp(SetOp *from)
 	COPY_SCALAR_FIELD(cmd);
 	COPY_SCALAR_FIELD(numCols);
 	COPY_POINTER_FIELD(dupColIdx, from->numCols * sizeof(AttrNumber));
+	COPY_POINTER_FIELD(dupOperators, from->numCols * sizeof(Oid));
 	COPY_SCALAR_FIELD(flagColIdx);
 
 	return newnode;
@@ -1356,6 +1365,7 @@ _copyInClauseInfo(InClauseInfo *from)
 	COPY_BITMAPSET_FIELD(lefthand);
 	COPY_BITMAPSET_FIELD(righthand);
 	COPY_NODE_FIELD(sub_targetlist);
+	COPY_NODE_FIELD(in_operators);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e1b3bbbbaa96aebc3374edde06821f045bddf3cc..fafd8ae5468a058b4880e915559a00fcc99b324f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.294 2007/01/09 02:14:12 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.295 2007/01/10 18:06:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -638,6 +638,7 @@ _equalInClauseInfo(InClauseInfo *a, InClauseInfo *b)
 	COMPARE_BITMAPSET_FIELD(lefthand);
 	COMPARE_BITMAPSET_FIELD(righthand);
 	COMPARE_NODE_FIELD(sub_targetlist);
+	COMPARE_NODE_FIELD(in_operators);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1ffaa08dfe988dae109d64fa66a549fa8d053e85..6137c39af0c21a48dc78d78e635a4c4fdcae0ece 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.292 2007/01/09 02:14:12 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.293 2007/01/10 18:06:03 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -437,13 +437,28 @@ _outNestLoop(StringInfo str, NestLoop *node)
 static void
 _outMergeJoin(StringInfo str, MergeJoin *node)
 {
+	int			numCols;
+	int			i;
+
 	WRITE_NODE_TYPE("MERGEJOIN");
 
 	_outJoinPlanInfo(str, (Join *) node);
 
 	WRITE_NODE_FIELD(mergeclauses);
-	WRITE_NODE_FIELD(mergefamilies);
-	WRITE_NODE_FIELD(mergestrategies);
+
+	numCols = list_length(node->mergeclauses);
+
+	appendStringInfo(str, " :mergeFamilies");
+	for (i = 0; i < numCols; i++)
+		appendStringInfo(str, " %u", node->mergeFamilies[i]);
+
+	appendStringInfo(str, " :mergeStrategies");
+	for (i = 0; i < numCols; i++)
+		appendStringInfo(str, " %d", node->mergeStrategies[i]);
+
+	appendStringInfo(str, " :mergeNullsFirst");
+	for (i = 0; i < numCols; i++)
+		appendStringInfo(str, " %d", (int) node->mergeNullsFirst[i]);
 }
 
 static void
@@ -482,6 +497,10 @@ _outGroup(StringInfo str, Group *node)
 	appendStringInfo(str, " :grpColIdx");
 	for (i = 0; i < node->numCols; i++)
 		appendStringInfo(str, " %d", node->grpColIdx[i]);
+
+	appendStringInfo(str, " :grpOperators");
+	for (i = 0; i < node->numCols; i++)
+		appendStringInfo(str, " %u", node->grpOperators[i]);
 }
 
 static void
@@ -530,6 +549,10 @@ _outUnique(StringInfo str, Unique *node)
 	appendStringInfo(str, " :uniqColIdx");
 	for (i = 0; i < node->numCols; i++)
 		appendStringInfo(str, " %d", node->uniqColIdx[i]);
+
+	appendStringInfo(str, " :uniqOperators");
+	for (i = 0; i < node->numCols; i++)
+		appendStringInfo(str, " %u", node->uniqOperators[i]);
 }
 
 static void
@@ -548,6 +571,10 @@ _outSetOp(StringInfo str, SetOp *node)
 	for (i = 0; i < node->numCols; i++)
 		appendStringInfo(str, " %d", node->dupColIdx[i]);
 
+	appendStringInfo(str, " :dupOperators");
+	for (i = 0; i < node->numCols; i++)
+		appendStringInfo(str, " %d", node->dupOperators[i]);
+
 	WRITE_INT_FIELD(flagColIdx);
 }
 
@@ -1169,13 +1196,29 @@ _outNestPath(StringInfo str, NestPath *node)
 static void
 _outMergePath(StringInfo str, MergePath *node)
 {
+	int			numCols;
+	int			i;
+
 	WRITE_NODE_TYPE("MERGEPATH");
 
 	_outJoinPathInfo(str, (JoinPath *) node);
 
 	WRITE_NODE_FIELD(path_mergeclauses);
-	WRITE_NODE_FIELD(path_mergefamilies);
-	WRITE_NODE_FIELD(path_mergestrategies);
+
+	numCols = list_length(node->path_mergeclauses);
+
+	appendStringInfo(str, " :path_mergeFamilies");
+	for (i = 0; i < numCols; i++)
+		appendStringInfo(str, " %u", node->path_mergeFamilies[i]);
+
+	appendStringInfo(str, " :path_mergeStrategies");
+	for (i = 0; i < numCols; i++)
+		appendStringInfo(str, " %d", node->path_mergeStrategies[i]);
+
+	appendStringInfo(str, " :path_mergeNullsFirst");
+	for (i = 0; i < numCols; i++)
+		appendStringInfo(str, " %d", (int) node->path_mergeNullsFirst[i]);
+
 	WRITE_NODE_FIELD(outersortkeys);
 	WRITE_NODE_FIELD(innersortkeys);
 }
@@ -1325,6 +1368,7 @@ _outInClauseInfo(StringInfo str, InClauseInfo *node)
 	WRITE_BITMAPSET_FIELD(lefthand);
 	WRITE_BITMAPSET_FIELD(righthand);
 	WRITE_NODE_FIELD(sub_targetlist);
+	WRITE_NODE_FIELD(in_operators);
 }
 
 static void
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 69d1a890e4be12c30a4f7f02d81d1b9bccd7652e..e1c003b5047a62e75c7b8a7a2b9f38cc273d196d 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -54,7 +54,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.173 2007/01/08 16:09:22 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.174 2007/01/10 18:06:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1258,8 +1258,8 @@ cost_mergejoin(MergePath *path, PlannerInfo *root)
 	Path	   *outer_path = path->jpath.outerjoinpath;
 	Path	   *inner_path = path->jpath.innerjoinpath;
 	List	   *mergeclauses = path->path_mergeclauses;
-	List	   *mergefamilies = path->path_mergefamilies;
-	List	   *mergestrategies = path->path_mergestrategies;
+	Oid		   *mergeFamilies = path->path_mergeFamilies;
+	int		   *mergeStrategies = path->path_mergeStrategies;
 	List	   *outersortkeys = path->outersortkeys;
 	List	   *innersortkeys = path->innersortkeys;
 	Cost		startup_cost = 0;
@@ -1357,8 +1357,8 @@ cost_mergejoin(MergePath *path, PlannerInfo *root)
 		firstclause = (RestrictInfo *) linitial(mergeclauses);
 		if (firstclause->left_mergescansel < 0) /* not computed yet? */
 			mergejoinscansel(root, (Node *) firstclause->clause,
-							 linitial_oid(mergefamilies),
-							 linitial_int(mergestrategies),
+							 mergeFamilies[0],
+							 mergeStrategies[0],
 							 &firstclause->left_mergescansel,
 							 &firstclause->right_mergescansel);
 
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index bde38af0dddc034eb5114728d2a59fece53efde6..2885b021546e6c0a0a7b081b190f4c40b4525e13 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.109 2007/01/05 22:19:31 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.110 2007/01/10 18:06:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -40,8 +40,10 @@ static List *select_mergejoin_clauses(RelOptInfo *joinrel,
 						 RelOptInfo *innerrel,
 						 List *restrictlist,
 						 JoinType jointype);
-static void build_mergejoin_strat_lists(List *mergeclauses,
-							List **mergefamilies, List **mergestrategies);
+static void build_mergejoin_strat_arrays(List *mergeclauses,
+										 Oid **mergefamilies,
+										 int **mergestrategies,
+										 bool **mergenullsfirst);
 
 
 /*
@@ -228,8 +230,9 @@ sort_inner_and_outer(PlannerInfo *root,
 		List	   *front_pathkey = (List *) lfirst(l);
 		List	   *cur_pathkeys;
 		List	   *cur_mergeclauses;
-		List	   *mergefamilies;
-		List	   *mergestrategies;
+		Oid		   *mergefamilies;
+		int		   *mergestrategies;
+		bool	   *mergenullsfirst;
 		List	   *outerkeys;
 		List	   *innerkeys;
 		List	   *merge_pathkeys;
@@ -275,8 +278,10 @@ sort_inner_and_outer(PlannerInfo *root,
 											 outerkeys);
 
 		/* Build opfamily info for execution */
-		build_mergejoin_strat_lists(cur_mergeclauses,
-									&mergefamilies, &mergestrategies);
+		build_mergejoin_strat_arrays(cur_mergeclauses,
+									 &mergefamilies,
+									 &mergestrategies,
+									 &mergenullsfirst);
 
 		/*
 		 * And now we can make the path.
@@ -292,6 +297,7 @@ sort_inner_and_outer(PlannerInfo *root,
 									   cur_mergeclauses,
 									   mergefamilies,
 									   mergestrategies,
+									   mergenullsfirst,
 									   outerkeys,
 									   innerkeys));
 	}
@@ -421,8 +427,9 @@ match_unsorted_outer(PlannerInfo *root,
 		Path	   *outerpath = (Path *) lfirst(l);
 		List	   *merge_pathkeys;
 		List	   *mergeclauses;
-		List	   *mergefamilies;
-		List	   *mergestrategies;
+		Oid		   *mergefamilies;
+		int		   *mergestrategies;
+		bool	   *mergenullsfirst;
 		List	   *innersortkeys;
 		List	   *trialsortkeys;
 		Path	   *cheapest_startup_inner;
@@ -530,8 +537,10 @@ match_unsorted_outer(PlannerInfo *root,
 													   innerrel);
 
 		/* Build opfamily info for execution */
-		build_mergejoin_strat_lists(mergeclauses,
-									&mergefamilies, &mergestrategies);
+		build_mergejoin_strat_arrays(mergeclauses,
+									 &mergefamilies,
+									 &mergestrategies,
+									 &mergenullsfirst);
 
 		/*
 		 * Generate a mergejoin on the basis of sorting the cheapest inner.
@@ -550,6 +559,7 @@ match_unsorted_outer(PlannerInfo *root,
 									   mergeclauses,
 									   mergefamilies,
 									   mergestrategies,
+									   mergenullsfirst,
 									   NIL,
 									   innersortkeys));
 
@@ -610,8 +620,10 @@ match_unsorted_outer(PlannerInfo *root,
 					newclauses = mergeclauses;
 
 				/* Build opfamily info for execution */
-				build_mergejoin_strat_lists(newclauses,
-											&mergefamilies, &mergestrategies);
+				build_mergejoin_strat_arrays(newclauses,
+											 &mergefamilies,
+											 &mergestrategies,
+											 &mergenullsfirst);
 
 				add_path(joinrel, (Path *)
 						 create_mergejoin_path(root,
@@ -624,6 +636,7 @@ match_unsorted_outer(PlannerInfo *root,
 											   newclauses,
 											   mergefamilies,
 											   mergestrategies,
+											   mergenullsfirst,
 											   NIL,
 											   NIL));
 				cheapest_total_inner = innerpath;
@@ -661,8 +674,10 @@ match_unsorted_outer(PlannerInfo *root,
 					}
 
 					/* Build opfamily info for execution */
-					build_mergejoin_strat_lists(newclauses,
-												&mergefamilies, &mergestrategies);
+					build_mergejoin_strat_arrays(newclauses,
+												 &mergefamilies,
+												 &mergestrategies,
+												 &mergenullsfirst);
 
 					add_path(joinrel, (Path *)
 							 create_mergejoin_path(root,
@@ -675,6 +690,7 @@ match_unsorted_outer(PlannerInfo *root,
 												   newclauses,
 												   mergefamilies,
 												   mergestrategies,
+												   mergenullsfirst,
 												   NIL,
 												   NIL));
 				}
@@ -981,20 +997,26 @@ select_mergejoin_clauses(RelOptInfo *joinrel,
 }
 
 /*
- * Temporary hack to build opfamily and strategy lists needed for mergejoin
+ * Temporary hack to build opfamily and strategy info needed for mergejoin
  * by the executor.  We need to rethink the planner's handling of merge
  * planning so that it can deal with multiple possible merge orders, but
  * that's not done yet.
  */
 static void
-build_mergejoin_strat_lists(List *mergeclauses,
-							List **mergefamilies, List **mergestrategies)
+build_mergejoin_strat_arrays(List *mergeclauses,
+							 Oid **mergefamilies,
+							 int **mergestrategies,
+							 bool **mergenullsfirst)
 {
+	int			nClauses = list_length(mergeclauses);
+	int			i;
 	ListCell   *l;
 
-	*mergefamilies = NIL;
-	*mergestrategies = NIL;
+	*mergefamilies = (Oid *) palloc(nClauses * sizeof(Oid));
+	*mergestrategies = (int *) palloc(nClauses * sizeof(int));
+	*mergenullsfirst = (bool *) palloc(nClauses * sizeof(bool));
 
+	i = 0;
 	foreach(l, mergeclauses)
 	{
 		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
@@ -1003,11 +1025,16 @@ build_mergejoin_strat_lists(List *mergeclauses,
 		 * We do not need to worry about whether the mergeclause will be
 		 * commuted at runtime --- it's the same opfamily either way.
 		 */
-		*mergefamilies = lappend_oid(*mergefamilies, restrictinfo->mergeopfamily);
+		(*mergefamilies)[i] = restrictinfo->mergeopfamily;
 		/*
 		 * For the moment, strategy must always be LessThan --- see
 		 * hack version of get_op_mergejoin_info
 		 */
-		*mergestrategies = lappend_int(*mergestrategies, BTLessStrategyNumber);
+		(*mergestrategies)[i] = BTLessStrategyNumber;
+
+		/* And we only allow NULLS LAST, too */
+		(*mergenullsfirst)[i] = false;
+
+		i++;
 	}
 }
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 8f1d8e81cc483d2f875743668cf579709c5411c9..9a8204392a11c10f8af412e3170f54a4a58614cb 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.220 2007/01/09 02:14:12 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.221 2007/01/10 18:06:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -113,7 +113,10 @@ static HashJoin *make_hashjoin(List *tlist,
 static Hash *make_hash(Plan *lefttree);
 static MergeJoin *make_mergejoin(List *tlist,
 			   List *joinclauses, List *otherclauses,
-			   List *mergeclauses, List *mergefamilies, List *mergestrategies,
+			   List *mergeclauses,
+			   Oid *mergefamilies,
+			   int *mergestrategies,
+			   bool *mergenullsfirst,
 			   Plan *lefttree, Plan *righttree,
 			   JoinType jointype);
 static Sort *make_sort(PlannerInfo *root, Plan *lefttree, int numCols,
@@ -595,6 +598,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 	Plan	   *plan;
 	Plan	   *subplan;
 	List	   *uniq_exprs;
+	List	   *in_operators;
 	List	   *newtlist;
 	int			nextresno;
 	bool		newitems;
@@ -626,10 +630,12 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 	 * To find the correct list of values to unique-ify, we look in the
 	 * information saved for IN expressions.  If this code is ever used in
 	 * other scenarios, some other way of finding what to unique-ify will
-	 * be needed.
+	 * be needed.  The IN clause's operators are needed too, since they
+	 * determine what the meaning of "unique" is in this context.
 	 *----------
 	 */
 	uniq_exprs = NIL;			/* just to keep compiler quiet */
+	in_operators = NIL;
 	foreach(l, root->in_info_list)
 	{
 		InClauseInfo *ininfo = (InClauseInfo *) lfirst(l);
@@ -637,6 +643,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 		if (bms_equal(ininfo->righthand, best_path->path.parent->relids))
 		{
 			uniq_exprs = ininfo->sub_targetlist;
+			in_operators = ininfo->in_operators;
 			break;
 		}
 	}
@@ -687,8 +694,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 	newtlist = subplan->targetlist;
 	numGroupCols = list_length(uniq_exprs);
 	groupColIdx = (AttrNumber *) palloc(numGroupCols * sizeof(AttrNumber));
-	groupColPos = 0;
 
+	groupColPos = 0;
 	foreach(l, uniq_exprs)
 	{
 		Node	   *uniqexpr = lfirst(l);
@@ -703,9 +710,30 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 	if (best_path->umethod == UNIQUE_PATH_HASH)
 	{
 		long		numGroups;
+		Oid		   *groupOperators;
 
 		numGroups = (long) Min(best_path->rows, (double) LONG_MAX);
 
+		/*
+		 * Get the (presumed hashable) equality operators for the Agg node
+		 * to use.  Normally these are the same as the IN clause operators,
+		 * but if those are cross-type operators then the equality operators
+		 * are the ones for the IN clause operators' RHS datatype.
+		 */
+		groupOperators = (Oid *) palloc(numGroupCols * sizeof(Oid));
+		groupColPos = 0;
+		foreach(l, in_operators)
+		{
+			Oid			in_oper = lfirst_oid(l);
+			Oid			eq_oper;
+
+			eq_oper = get_compatible_hash_operator(in_oper, false);
+			if (!OidIsValid(eq_oper))		/* shouldn't happen */
+				elog(ERROR, "could not find compatible hash operator for operator %u",
+					 in_oper);
+			groupOperators[groupColPos++] = eq_oper;
+		}
+
 		/*
 		 * Since the Agg node is going to project anyway, we can give it the
 		 * minimum output tlist, without any stuff we might have added to the
@@ -717,6 +745,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 								 AGG_HASHED,
 								 numGroupCols,
 								 groupColIdx,
+								 groupOperators,
 								 numGroups,
 								 0,
 								 subplan);
@@ -725,18 +754,29 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 	{
 		List	   *sortList = NIL;
 
-		for (groupColPos = 0; groupColPos < numGroupCols; groupColPos++)
+		/* Create an ORDER BY list to sort the input compatibly */
+		groupColPos = 0;
+		foreach(l, in_operators)
 		{
+			Oid			in_oper = lfirst_oid(l);
+			Oid			sortop;
 			TargetEntry *tle;
+			SortClause *sortcl;
 
+			sortop = get_ordering_op_for_equality_op(in_oper, false);
+			if (!OidIsValid(sortop))		/* shouldn't happen */
+				elog(ERROR, "could not find ordering operator for equality operator %u",
+					 in_oper);
 			tle = get_tle_by_resno(subplan->targetlist,
 								   groupColIdx[groupColPos]);
 			Assert(tle != NULL);
-			sortList = addTargetToSortList(NULL, tle,
-										   sortList, subplan->targetlist,
-										   SORTBY_DEFAULT,
-										   SORTBY_NULLS_DEFAULT,
-										   NIL, false);
+			sortcl = makeNode(SortClause);
+			sortcl->tleSortGroupRef = assignSortGroupRef(tle,
+														 subplan->targetlist);
+			sortcl->sortop = sortop;
+			sortcl->nulls_first = false;
+			sortList = lappend(sortList, sortcl);
+			groupColPos++;
 		}
 		plan = (Plan *) make_sort_from_sortclauses(root, sortList, subplan);
 		plan = (Plan *) make_unique(plan, sortList);
@@ -1542,8 +1582,9 @@ create_mergejoin_plan(PlannerInfo *root,
 							   joinclauses,
 							   otherclauses,
 							   mergeclauses,
-							   best_path->path_mergefamilies,
-							   best_path->path_mergestrategies,
+							   best_path->path_mergeFamilies,
+							   best_path->path_mergeStrategies,
+							   best_path->path_mergeNullsFirst,
 							   outer_plan,
 							   inner_plan,
 							   best_path->jpath.jointype);
@@ -2335,8 +2376,9 @@ make_mergejoin(List *tlist,
 			   List *joinclauses,
 			   List *otherclauses,
 			   List *mergeclauses,
-			   List *mergefamilies,
-			   List *mergestrategies,
+			   Oid *mergefamilies,
+			   int *mergestrategies,
+			   bool *mergenullsfirst,
 			   Plan *lefttree,
 			   Plan *righttree,
 			   JoinType jointype)
@@ -2350,8 +2392,9 @@ make_mergejoin(List *tlist,
 	plan->lefttree = lefttree;
 	plan->righttree = righttree;
 	node->mergeclauses = mergeclauses;
-	node->mergefamilies = mergefamilies;
-	node->mergestrategies = mergestrategies;
+	node->mergeFamilies = mergefamilies;
+	node->mergeStrategies = mergestrategies;
+	node->mergeNullsFirst = mergenullsfirst;
 	node->join.jointype = jointype;
 	node->join.joinqual = joinclauses;
 
@@ -2613,7 +2656,7 @@ make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree)
  * This might look like it could be merged with make_sort_from_sortclauses,
  * but presently we *must* use the grpColIdx[] array to locate sort columns,
  * because the child plan's tlist is not marked with ressortgroupref info
- * appropriate to the grouping node.  So, only the sort direction info
+ * appropriate to the grouping node.  So, only the sort ordering info
  * is used from the GroupClause entries.
  */
 Sort *
@@ -2716,7 +2759,7 @@ materialize_finished_plan(Plan *subplan)
 Agg *
 make_agg(PlannerInfo *root, List *tlist, List *qual,
 		 AggStrategy aggstrategy,
-		 int numGroupCols, AttrNumber *grpColIdx,
+		 int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
 		 long numGroups, int numAggs,
 		 Plan *lefttree)
 {
@@ -2728,6 +2771,7 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
 	node->aggstrategy = aggstrategy;
 	node->numCols = numGroupCols;
 	node->grpColIdx = grpColIdx;
+	node->grpOperators = grpOperators;
 	node->numGroups = numGroups;
 
 	copy_plan_costsize(plan, lefttree); /* only care about copying size */
@@ -2784,6 +2828,7 @@ make_group(PlannerInfo *root,
 		   List *qual,
 		   int numGroupCols,
 		   AttrNumber *grpColIdx,
+		   Oid *grpOperators,
 		   double numGroups,
 		   Plan *lefttree)
 {
@@ -2794,6 +2839,7 @@ make_group(PlannerInfo *root,
 
 	node->numCols = numGroupCols;
 	node->grpColIdx = grpColIdx;
+	node->grpOperators = grpOperators;
 
 	copy_plan_costsize(plan, lefttree); /* only care about copying size */
 	cost_group(&group_path, root,
@@ -2841,7 +2887,8 @@ make_group(PlannerInfo *root,
 
 /*
  * distinctList is a list of SortClauses, identifying the targetlist items
- * that should be considered by the Unique filter.
+ * that should be considered by the Unique filter.  The input path must
+ * already be sorted accordingly.
  */
 Unique *
 make_unique(Plan *lefttree, List *distinctList)
@@ -2851,6 +2898,7 @@ make_unique(Plan *lefttree, List *distinctList)
 	int			numCols = list_length(distinctList);
 	int			keyno = 0;
 	AttrNumber *uniqColIdx;
+	Oid		   *uniqOperators;
 	ListCell   *slitem;
 
 	copy_plan_costsize(plan, lefttree);
@@ -2874,28 +2922,37 @@ make_unique(Plan *lefttree, List *distinctList)
 	plan->righttree = NULL;
 
 	/*
-	 * convert SortClause list into array of attr indexes, as wanted by exec
+	 * convert SortClause list into arrays of attr indexes and equality
+	 * operators, as wanted by executor
 	 */
 	Assert(numCols > 0);
 	uniqColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols);
+	uniqOperators = (Oid *) palloc(sizeof(Oid) * numCols);
 
 	foreach(slitem, distinctList)
 	{
 		SortClause *sortcl = (SortClause *) lfirst(slitem);
 		TargetEntry *tle = get_sortgroupclause_tle(sortcl, plan->targetlist);
 
-		uniqColIdx[keyno++] = tle->resno;
+		uniqColIdx[keyno] = tle->resno;
+		uniqOperators[keyno] = get_equality_op_for_ordering_op(sortcl->sortop);
+		if (!OidIsValid(uniqOperators[keyno]))		/* shouldn't happen */
+			elog(ERROR, "could not find equality operator for ordering operator %u",
+				 sortcl->sortop);
+		keyno++;
 	}
 
 	node->numCols = numCols;
 	node->uniqColIdx = uniqColIdx;
+	node->uniqOperators = uniqOperators;
 
 	return node;
 }
 
 /*
  * distinctList is a list of SortClauses, identifying the targetlist items
- * that should be considered by the SetOp filter.
+ * that should be considered by the SetOp filter.  The input path must
+ * already be sorted accordingly.
  */
 SetOp *
 make_setop(SetOpCmd cmd, Plan *lefttree,
@@ -2906,6 +2963,7 @@ make_setop(SetOpCmd cmd, Plan *lefttree,
 	int			numCols = list_length(distinctList);
 	int			keyno = 0;
 	AttrNumber *dupColIdx;
+	Oid		   *dupOperators;
 	ListCell   *slitem;
 
 	copy_plan_costsize(plan, lefttree);
@@ -2930,22 +2988,30 @@ make_setop(SetOpCmd cmd, Plan *lefttree,
 	plan->righttree = NULL;
 
 	/*
-	 * convert SortClause list into array of attr indexes, as wanted by exec
+	 * convert SortClause list into arrays of attr indexes and equality
+	 * operators, as wanted by executor
 	 */
 	Assert(numCols > 0);
 	dupColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols);
+	dupOperators = (Oid *) palloc(sizeof(Oid) * numCols);
 
 	foreach(slitem, distinctList)
 	{
 		SortClause *sortcl = (SortClause *) lfirst(slitem);
 		TargetEntry *tle = get_sortgroupclause_tle(sortcl, plan->targetlist);
 
-		dupColIdx[keyno++] = tle->resno;
+		dupColIdx[keyno] = tle->resno;
+		dupOperators[keyno] = get_equality_op_for_ordering_op(sortcl->sortop);
+		if (!OidIsValid(dupOperators[keyno]))		/* shouldn't happen */
+			elog(ERROR, "could not find equality operator for ordering operator %u",
+				 sortcl->sortop);
+		keyno++;
 	}
 
 	node->cmd = cmd;
 	node->numCols = numCols;
 	node->dupColIdx = dupColIdx;
+	node->dupOperators = dupOperators;
 	node->flagColIdx = flagColIdx;
 
 	return node;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index a4de2dd36a55fb629b1695b4c194418009c0fe21..5fcfc58bf9b3acc642c3a4600dec96a06b55ab2e 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.210 2007/01/05 22:19:32 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.211 2007/01/10 18:06:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -38,6 +38,7 @@
 #include "parser/parse_expr.h"
 #include "parser/parse_oper.h"
 #include "parser/parsetree.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
 
@@ -62,10 +63,11 @@ static bool is_dummy_plan(Plan *plan);
 static double preprocess_limit(PlannerInfo *root,
 				 double tuple_fraction,
 				 int64 *offset_est, int64 *count_est);
+static Oid *extract_grouping_ops(List *groupClause);
 static bool choose_hashed_grouping(PlannerInfo *root, double tuple_fraction,
 					   Path *cheapest_path, Path *sorted_path,
-					   double dNumGroups, AggClauseCounts *agg_counts);
-static bool hash_safe_grouping(PlannerInfo *root);
+					   Oid *groupOperators, double dNumGroups,
+					   AggClauseCounts *agg_counts);
 static List *make_subplanTargetList(PlannerInfo *root, List *tlist,
 					   AttrNumber **groupColIdx, bool *need_tlist_eval);
 static void locate_grouping_columns(PlannerInfo *root,
@@ -750,6 +752,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 		List	   *sub_tlist;
 		List	   *group_pathkeys;
 		AttrNumber *groupColIdx = NULL;
+		Oid		   *groupOperators = NULL;
 		bool		need_tlist_eval = true;
 		QualCost	tlist_cost;
 		Path	   *cheapest_path;
@@ -829,14 +832,17 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 		sort_pathkeys = root->sort_pathkeys;
 
 		/*
-		 * If grouping, decide whether we want to use hashed grouping.
+		 * If grouping, extract the grouping operators and decide whether we
+		 * want to use hashed grouping.
 		 */
 		if (parse->groupClause)
 		{
+			groupOperators = extract_grouping_ops(parse->groupClause);
 			use_hashed_grouping =
 				choose_hashed_grouping(root, tuple_fraction,
 									   cheapest_path, sorted_path,
-									   dNumGroups, &agg_counts);
+									   groupOperators, dNumGroups,
+									   &agg_counts);
 
 			/* Also convert # groups to long int --- but 'ware overflow! */
 			numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
@@ -956,6 +962,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 												AGG_HASHED,
 												numGroupCols,
 												groupColIdx,
+												groupOperators,
 												numGroups,
 												agg_counts.numAggs,
 												result_plan);
@@ -999,6 +1006,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 												aggstrategy,
 												numGroupCols,
 												groupColIdx,
+												groupOperators,
 												numGroups,
 												agg_counts.numAggs,
 												result_plan);
@@ -1027,6 +1035,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 												  (List *) parse->havingQual,
 												  numGroupCols,
 												  groupColIdx,
+												  groupOperators,
 												  dNumGroups,
 												  result_plan);
 				/* The Group node won't change sort ordering */
@@ -1337,13 +1346,42 @@ preprocess_limit(PlannerInfo *root, double tuple_fraction,
 	return tuple_fraction;
 }
 
+/*
+ * extract_grouping_ops - make an array of the equality operator OIDs
+ *		for the GROUP BY clause
+ */
+static Oid *
+extract_grouping_ops(List *groupClause)
+{
+	int			numCols = list_length(groupClause);
+	int			colno = 0;
+	Oid		   *groupOperators;
+	ListCell   *glitem;
+
+	groupOperators = (Oid *) palloc(sizeof(Oid) * numCols);
+
+	foreach(glitem, groupClause)
+	{
+		GroupClause *groupcl = (GroupClause *) lfirst(glitem);
+
+		groupOperators[colno] = get_equality_op_for_ordering_op(groupcl->sortop);
+		if (!OidIsValid(groupOperators[colno]))		/* shouldn't happen */
+			elog(ERROR, "could not find equality operator for ordering operator %u",
+				 groupcl->sortop);
+		colno++;
+	}
+
+	return groupOperators;
+}
+
 /*
  * choose_hashed_grouping - should we use hashed grouping?
  */
 static bool
 choose_hashed_grouping(PlannerInfo *root, double tuple_fraction,
 					   Path *cheapest_path, Path *sorted_path,
-					   double dNumGroups, AggClauseCounts *agg_counts)
+					   Oid *groupOperators, double dNumGroups,
+					   AggClauseCounts *agg_counts)
 {
 	int			numGroupCols = list_length(root->parse->groupClause);
 	double		cheapest_path_rows;
@@ -1352,10 +1390,13 @@ choose_hashed_grouping(PlannerInfo *root, double tuple_fraction,
 	List	   *current_pathkeys;
 	Path		hashed_p;
 	Path		sorted_p;
+	int			i;
 
 	/*
 	 * Check can't-do-it conditions, including whether the grouping operators
-	 * are hashjoinable.
+	 * are hashjoinable.  (We assume hashing is OK if they are marked
+	 * oprcanhash.  If there isn't actually a supporting hash function,
+	 * the executor will complain at runtime.)
 	 *
 	 * Executor doesn't support hashed aggregation with DISTINCT aggregates.
 	 * (Doing so would imply storing *all* the input values in the hash table,
@@ -1365,8 +1406,11 @@ choose_hashed_grouping(PlannerInfo *root, double tuple_fraction,
 		return false;
 	if (agg_counts->numDistinctAggs != 0)
 		return false;
-	if (!hash_safe_grouping(root))
-		return false;
+	for (i = 0; i < numGroupCols; i++)
+	{
+		if (!op_hashjoinable(groupOperators[i]))
+			return false;
+	}
 
 	/*
 	 * Don't do it if it doesn't look like the hashtable will fit into
@@ -1471,36 +1515,6 @@ choose_hashed_grouping(PlannerInfo *root, double tuple_fraction,
 	return false;
 }
 
-/*
- * hash_safe_grouping - are grouping operators hashable?
- *
- * We assume hashed aggregation will work if the datatype's equality operator
- * is marked hashjoinable.
- */
-static bool
-hash_safe_grouping(PlannerInfo *root)
-{
-	ListCell   *gl;
-
-	foreach(gl, root->parse->groupClause)
-	{
-		GroupClause *grpcl = (GroupClause *) lfirst(gl);
-		TargetEntry *tle = get_sortgroupclause_tle(grpcl,
-												   root->parse->targetList);
-		Operator	optup;
-		bool		oprcanhash;
-
-		optup = equality_oper(exprType((Node *) tle->expr), true);
-		if (!optup)
-			return false;
-		oprcanhash = ((Form_pg_operator) GETSTRUCT(optup))->oprcanhash;
-		ReleaseSysCache(optup);
-		if (!oprcanhash)
-			return false;
-	}
-	return true;
-}
-
 /*---------------
  * make_subplanTargetList
  *	  Generate appropriate target list when grouping is required.
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index ed76612dc0a4ab4803cc6ea4536ac88815bb9d54..7339445e04611422d92d618e43036ca8f2233fa2 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.116 2007/01/05 22:19:32 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.117 2007/01/10 18:06:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -671,11 +671,13 @@ convert_IN_to_join(PlannerInfo *root, SubLink *sublink)
 {
 	Query	   *parse = root->parse;
 	Query	   *subselect = (Query *) sublink->subselect;
+	List	   *in_operators;
 	Relids		left_varnos;
 	int			rtindex;
 	RangeTblEntry *rte;
 	RangeTblRef *rtr;
 	InClauseInfo *ininfo;
+	Node	   *result;
 
 	/*
 	 * The sublink type must be "= ANY" --- that is, an IN operator.  We
@@ -689,15 +691,31 @@ convert_IN_to_join(PlannerInfo *root, SubLink *sublink)
 		return NULL;
 	if (sublink->testexpr && IsA(sublink->testexpr, OpExpr))
 	{
+		Oid			opno = ((OpExpr *) sublink->testexpr)->opno;
 		List	   *opfamilies;
 		List	   *opstrats;
 
-		get_op_btree_interpretation(((OpExpr *) sublink->testexpr)->opno,
-									&opfamilies, &opstrats);
+		get_op_btree_interpretation(opno, &opfamilies, &opstrats);
 		if (!list_member_int(opstrats, ROWCOMPARE_EQ))
 			return NULL;
+		in_operators = list_make1_oid(opno);
+	}
+	else if (and_clause(sublink->testexpr))
+	{
+		ListCell   *lc;
+
+		/* OK, but we need to extract the per-column operator OIDs */
+		in_operators = NIL;
+		foreach(lc, ((BoolExpr *) sublink->testexpr)->args)
+		{
+			OpExpr *op = (OpExpr *) lfirst(lc);
+
+			if (!IsA(op, OpExpr))		/* probably shouldn't happen */
+				return NULL;
+			in_operators = lappend_oid(in_operators, op->opno);
+		}
 	}
-	else if (!and_clause(sublink->testexpr))
+	else
 		return NULL;
 
 	/*
@@ -745,16 +763,23 @@ convert_IN_to_join(PlannerInfo *root, SubLink *sublink)
 	ininfo = makeNode(InClauseInfo);
 	ininfo->lefthand = left_varnos;
 	ininfo->righthand = bms_make_singleton(rtindex);
-	root->in_info_list = lappend(root->in_info_list, ininfo);
+	ininfo->in_operators = in_operators;
 
 	/*
 	 * Build the result qual expression.  As a side effect,
 	 * ininfo->sub_targetlist is filled with a list of Vars representing the
 	 * subselect outputs.
 	 */
-	return convert_testexpr(sublink->testexpr,
-							rtindex,
-							&ininfo->sub_targetlist);
+	result = convert_testexpr(sublink->testexpr,
+							  rtindex,
+							  &ininfo->sub_targetlist);
+
+	Assert(list_length(in_operators) == list_length(ininfo->sub_targetlist));
+
+	/* Add the completed node to the query's list */
+	root->in_info_list = lappend(root->in_info_list, ininfo);
+
+	return result;
 }
 
 /*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 3250beafb679c9275bc8af57e1ae78c97333a1e2..4858f985cffe2eb0f1f061a866aba3d7a996c37a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.228 2007/01/09 02:14:13 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.229 2007/01/10 18:06:04 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -3995,6 +3995,7 @@ expression_tree_mutator(Node *node,
 
 				FLATCOPY(newnode, ininfo, InClauseInfo);
 				MUTATE(newnode->sub_targetlist, ininfo->sub_targetlist, List *);
+				/* Assume we need not make a copy of in_operators list */
 				return (Node *) newnode;
 			}
 			break;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index e1390aa11122d3ee2d29996c810b3d1dc39423eb..8ee6346e5b18d56c05d2df13f2aa0e5772c6659e 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.135 2007/01/05 22:19:32 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.136 2007/01/10 18:06:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -28,12 +28,14 @@
 #include "parser/parsetree.h"
 #include "utils/memutils.h"
 #include "utils/selfuncs.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
 
 static List *translate_sub_tlist(List *tlist, int relid);
-static bool query_is_distinct_for(Query *query, List *colnos);
-static bool hash_safe_tlist(List *tlist);
+static bool query_is_distinct_for(Query *query, List *colnos, List *opids);
+static Oid	distinct_col_search(int colno, List *colnos, List *opids);
+static bool hash_safe_operators(List *opids);
 
 
 /*****************************************************************************
@@ -733,6 +735,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath)
 	Path		agg_path;		/* dummy for result of cost_agg */
 	MemoryContext oldcontext;
 	List	   *sub_targetlist;
+	List	   *in_operators;
 	ListCell   *l;
 	int			numCols;
 
@@ -769,9 +772,11 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath)
 	/*
 	 * Try to identify the targetlist that will actually be unique-ified. In
 	 * current usage, this routine is only used for sub-selects of IN clauses,
-	 * so we should be able to find the tlist in in_info_list.
+	 * so we should be able to find the tlist in in_info_list.  Get the IN
+	 * clause's operators, too, because they determine what "unique" means.
 	 */
 	sub_targetlist = NIL;
+	in_operators = NIL;
 	foreach(l, root->in_info_list)
 	{
 		InClauseInfo *ininfo = (InClauseInfo *) lfirst(l);
@@ -779,6 +784,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath)
 		if (bms_equal(ininfo->righthand, rel->relids))
 		{
 			sub_targetlist = ininfo->sub_targetlist;
+			in_operators = ininfo->in_operators;
 			break;
 		}
 	}
@@ -801,7 +807,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath)
 		sub_tlist_colnos = translate_sub_tlist(sub_targetlist, rel->relid);
 
 		if (sub_tlist_colnos &&
-			query_is_distinct_for(rte->subquery, sub_tlist_colnos))
+			query_is_distinct_for(rte->subquery,
+								  sub_tlist_colnos, in_operators))
 		{
 			pathnode->umethod = UNIQUE_PATH_NOOP;
 			pathnode->rows = rel->rows;
@@ -847,11 +854,11 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath)
 
 	/*
 	 * Is it safe to use a hashed implementation?  If so, estimate and compare
-	 * costs.  We only try this if we know the targetlist for sure (else we
-	 * can't be sure about the datatypes involved).
+	 * costs.  We only try this if we know the IN operators, else we can't
+	 * check their hashability.
 	 */
 	pathnode->umethod = UNIQUE_PATH_SORT;
-	if (enable_hashagg && sub_targetlist && hash_safe_tlist(sub_targetlist))
+	if (enable_hashagg && in_operators && hash_safe_operators(in_operators))
 	{
 		/*
 		 * Estimate the overhead per hashtable entry at 64 bytes (same as in
@@ -924,16 +931,25 @@ translate_sub_tlist(List *tlist, int relid)
  *
  * colnos is an integer list of output column numbers (resno's).  We are
  * interested in whether rows consisting of just these columns are certain
- * to be distinct.
+ * to be distinct.  "Distinctness" is defined according to whether the
+ * corresponding upper-level equality operators listed in opids would think
+ * the values are distinct.  (Note: the opids entries could be cross-type
+ * operators, and thus not exactly the equality operators that the subquery
+ * would use itself.  We assume that the subquery is compatible if these
+ * operators appear in the same btree opfamily as the ones the subquery uses.)
  */
 static bool
-query_is_distinct_for(Query *query, List *colnos)
+query_is_distinct_for(Query *query, List *colnos, List *opids)
 {
 	ListCell   *l;
+	Oid			opid;
+
+	Assert(list_length(colnos) == list_length(opids));
 
 	/*
 	 * DISTINCT (including DISTINCT ON) guarantees uniqueness if all the
-	 * columns in the DISTINCT clause appear in colnos.
+	 * columns in the DISTINCT clause appear in colnos and operator
+	 * semantics match.
 	 */
 	if (query->distinctClause)
 	{
@@ -943,7 +959,9 @@ query_is_distinct_for(Query *query, List *colnos)
 			TargetEntry *tle = get_sortgroupclause_tle(scl,
 													   query->targetList);
 
-			if (!list_member_int(colnos, tle->resno))
+			opid = distinct_col_search(tle->resno, colnos, opids);
+			if (!OidIsValid(opid) ||
+				!ops_in_same_btree_opfamily(opid, scl->sortop))
 				break;			/* exit early if no match */
 		}
 		if (l == NULL)			/* had matches for all? */
@@ -952,7 +970,7 @@ query_is_distinct_for(Query *query, List *colnos)
 
 	/*
 	 * Similarly, GROUP BY guarantees uniqueness if all the grouped columns
-	 * appear in colnos.
+	 * appear in colnos and operator semantics match.
 	 */
 	if (query->groupClause)
 	{
@@ -962,7 +980,9 @@ query_is_distinct_for(Query *query, List *colnos)
 			TargetEntry *tle = get_sortgroupclause_tle(grpcl,
 													   query->targetList);
 
-			if (!list_member_int(colnos, tle->resno))
+			opid = distinct_col_search(tle->resno, colnos, opids);
+			if (!OidIsValid(opid) ||
+				!ops_in_same_btree_opfamily(opid, grpcl->sortop))
 				break;			/* exit early if no match */
 		}
 		if (l == NULL)			/* had matches for all? */
@@ -972,7 +992,7 @@ query_is_distinct_for(Query *query, List *colnos)
 	{
 		/*
 		 * If we have no GROUP BY, but do have aggregates or HAVING, then the
-		 * result is at most one row so it's surely unique.
+		 * result is at most one row so it's surely unique, for any operators.
 		 */
 		if (query->hasAggs || query->havingQual)
 			return true;
@@ -980,7 +1000,13 @@ query_is_distinct_for(Query *query, List *colnos)
 
 	/*
 	 * UNION, INTERSECT, EXCEPT guarantee uniqueness of the whole output row,
-	 * except with ALL
+	 * except with ALL.
+	 *
+	 * XXX this code knows that prepunion.c will adopt the default ordering
+	 * operator for each column datatype as the sortop.  It'd probably be
+	 * better if these operators were chosen at parse time and stored into
+	 * the parsetree, instead of leaving bits of the planner to decide
+	 * semantics.
 	 */
 	if (query->setOperations)
 	{
@@ -996,8 +1022,13 @@ query_is_distinct_for(Query *query, List *colnos)
 			{
 				TargetEntry *tle = (TargetEntry *) lfirst(l);
 
-				if (!tle->resjunk &&
-					!list_member_int(colnos, tle->resno))
+				if (tle->resjunk)
+					continue;	/* ignore resjunk columns */
+
+				opid = distinct_col_search(tle->resno, colnos, opids);
+				if (!OidIsValid(opid) ||
+					!ops_in_same_btree_opfamily(opid,
+												ordering_oper_opid(exprType((Node *) tle->expr))))
 					break;		/* exit early if no match */
 			}
 			if (l == NULL)		/* had matches for all? */
@@ -1014,31 +1045,46 @@ query_is_distinct_for(Query *query, List *colnos)
 }
 
 /*
- * hash_safe_tlist - can datatypes of given tlist be hashed?
+ * distinct_col_search - subroutine for query_is_distinct_for
  *
- * We assume hashed aggregation will work if the datatype's equality operator
- * is marked hashjoinable.
+ * If colno is in colnos, return the corresponding element of opids,
+ * else return InvalidOid.  (We expect colnos does not contain duplicates,
+ * so the result is well-defined.)
+ */
+static Oid
+distinct_col_search(int colno, List *colnos, List *opids)
+{
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, colnos, lc2, opids)
+	{
+		if (colno == lfirst_int(lc1))
+			return lfirst_oid(lc2);
+	}
+	return InvalidOid;
+}
+
+/*
+ * hash_safe_operators - can all the specified IN operators be hashed?
  *
- * XXX this probably should be somewhere else.	See also hash_safe_grouping
- * in plan/planner.c.
+ * We assume hashed aggregation will work if each IN operator is marked
+ * hashjoinable.  If the IN operators are cross-type, this could conceivably
+ * fail: the aggregation will need a hashable equality operator for the RHS
+ * datatype --- but it's pretty hard to conceive of a hash opclass that has
+ * cross-type hashing without support for hashing the individual types, so
+ * we don't expend cycles here to support the case.  We could check
+ * get_compatible_hash_operator() instead of just op_hashjoinable(), but the
+ * former is a significantly more expensive test.
  */
 static bool
-hash_safe_tlist(List *tlist)
+hash_safe_operators(List *opids)
 {
-	ListCell   *tl;
+	ListCell   *lc;
 
-	foreach(tl, tlist)
+	foreach(lc, opids)
 	{
-		Node	   *expr = (Node *) lfirst(tl);
-		Operator	optup;
-		bool		oprcanhash;
-
-		optup = equality_oper(exprType(expr), true);
-		if (!optup)
-			return false;
-		oprcanhash = ((Form_pg_operator) GETSTRUCT(optup))->oprcanhash;
-		ReleaseSysCache(optup);
-		if (!oprcanhash)
+		if (!op_hashjoinable(lfirst_oid(lc)))
 			return false;
 	}
 	return true;
@@ -1156,6 +1202,7 @@ create_nestloop_path(PlannerInfo *root,
  *		ordering for each merge clause
  * 'mergestrategies' are the btree operator strategies identifying the merge
  *		ordering for each merge clause
+ * 'mergenullsfirst' are the nulls first/last flags for each merge clause
  * 'outersortkeys' are the sort varkeys for the outer relation
  * 'innersortkeys' are the sort varkeys for the inner relation
  */
@@ -1168,8 +1215,9 @@ create_mergejoin_path(PlannerInfo *root,
 					  List *restrict_clauses,
 					  List *pathkeys,
 					  List *mergeclauses,
-					  List *mergefamilies,
-					  List *mergestrategies,
+					  Oid *mergefamilies,
+					  int *mergestrategies,
+					  bool *mergenullsfirst,
 					  List *outersortkeys,
 					  List *innersortkeys)
 {
@@ -1210,8 +1258,9 @@ create_mergejoin_path(PlannerInfo *root,
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
 	pathnode->jpath.path.pathkeys = pathkeys;
 	pathnode->path_mergeclauses = mergeclauses;
-	pathnode->path_mergefamilies = mergefamilies;
-	pathnode->path_mergestrategies = mergestrategies;
+	pathnode->path_mergeFamilies = mergefamilies;
+	pathnode->path_mergeStrategies = mergestrategies;
+	pathnode->path_mergeNullsFirst = mergenullsfirst;
 	pathnode->outersortkeys = outersortkeys;
 	pathnode->innersortkeys = innersortkeys;
 
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 6db3fce8377ad301807776537da9f85bc03d4e49..9792671fec6c05dd8d3289aa5f3c69dab0215d6c 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.162 2007/01/09 02:14:14 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.163 2007/01/10 18:06:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1676,7 +1676,8 @@ addTargetToSortList(ParseState *pstate, TargetEntry *tle,
 			 * Verify it's a valid ordering operator, and determine
 			 * whether to consider it like ASC or DESC.
 			 */
-			if (!get_op_compare_function(sortop, &cmpfunc, &reverse))
+			if (!get_compare_function_for_ordering_op(sortop,
+													  &cmpfunc, &reverse))
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("operator %s is not a valid ordering operator",
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 6379e25812694a2f0d25064bbc626852ace07bae..40a74594facac48c8e3ea94a6dfce702e3a85302 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.142 2007/01/09 02:14:14 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.143 2007/01/10 18:06:04 tgl Exp $
  *
  * NOTES
  *	  Eventually, the index information should go through here, too.
@@ -286,7 +286,7 @@ get_op_mergejoin_info(Oid eq_op, Oid *left_sortop,
 #endif
 
 /*
- * get_op_compare_function
+ * get_compare_function_for_ordering_op
  *		Get the OID of the datatype-specific btree comparison function
  *		associated with an ordering operator (a "<" or ">" operator).
  *
@@ -298,7 +298,7 @@ get_op_mergejoin_info(Oid eq_op, Oid *left_sortop,
  * (This indicates that the operator is not a valid ordering operator.)
  */
 bool
-get_op_compare_function(Oid opno, Oid *cmpfunc, bool *reverse)
+get_compare_function_for_ordering_op(Oid opno, Oid *cmpfunc, bool *reverse)
 {
 	bool		result = false;
 	CatCList   *catlist;
@@ -357,6 +357,177 @@ get_op_compare_function(Oid opno, Oid *cmpfunc, bool *reverse)
 	return result;
 }
 
+/*
+ * get_equality_op_for_ordering_op
+ *		Get the OID of the datatype-specific btree equality operator
+ *		associated with an ordering operator (a "<" or ">" operator).
+ *
+ * Returns InvalidOid if no matching equality operator can be found.
+ * (This indicates that the operator is not a valid ordering operator.)
+ */
+Oid
+get_equality_op_for_ordering_op(Oid opno)
+{
+	Oid			result = InvalidOid;
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * Search pg_amop to see if the target operator is registered as the "<"
+	 * or ">" operator of any btree opfamily.  This is exactly like
+	 * get_compare_function_for_ordering_op except we don't care whether the
+	 * ordering op is "<" or ">" ... the equality operator will be the same
+	 * either way.
+	 */
+	catlist = SearchSysCacheList(AMOPOPID, 1,
+								 ObjectIdGetDatum(opno),
+								 0, 0, 0);
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		/* must be btree */
+		if (aform->amopmethod != BTREE_AM_OID)
+			continue;
+
+		if (aform->amopstrategy == BTLessStrategyNumber ||
+			aform->amopstrategy == BTGreaterStrategyNumber)
+		{
+			/* Found a suitable opfamily, get matching equality operator */
+			result = get_opfamily_member(aform->amopfamily,
+										 aform->amoplefttype,
+										 aform->amoprighttype,
+										 BTEqualStrategyNumber);
+			if (OidIsValid(result))
+				break;
+			/* failure probably shouldn't happen, but keep looking if so */
+		}
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
+/*
+ * get_ordering_op_for_equality_op
+ *		Get the OID of a datatype-specific btree ordering operator
+ *		associated with an equality operator.  (If there are multiple
+ *		possibilities, assume any one will do.)
+ *
+ * This function is used when we have to sort data before unique-ifying,
+ * and don't much care which sorting op is used as long as it's compatible
+ * with the intended equality operator.  Since we need a sorting operator,
+ * it should be single-data-type even if the given operator is cross-type.
+ * The caller specifies whether to find an op for the LHS or RHS data type.
+ *
+ * Returns InvalidOid if no matching ordering operator can be found.
+ */
+Oid
+get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type)
+{
+	Oid			result = InvalidOid;
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * Search pg_amop to see if the target operator is registered as the "="
+	 * operator of any btree opfamily.
+	 */
+	catlist = SearchSysCacheList(AMOPOPID, 1,
+								 ObjectIdGetDatum(opno),
+								 0, 0, 0);
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		/* must be btree */
+		if (aform->amopmethod != BTREE_AM_OID)
+			continue;
+
+		if (aform->amopstrategy == BTEqualStrategyNumber)
+		{
+			/* Found a suitable opfamily, get matching ordering operator */
+			Oid		typid;
+
+			typid = use_lhs_type ? aform->amoplefttype : aform->amoprighttype;
+			result = get_opfamily_member(aform->amopfamily,
+										 typid, typid,
+										 BTLessStrategyNumber);
+			if (OidIsValid(result))
+				break;
+			/* failure probably shouldn't happen, but keep looking if so */
+		}
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
+/*
+ * get_compatible_hash_operator
+ *		Get the OID of a hash equality operator compatible with the given
+ *		operator, but operating on its LHS or RHS datatype as specified.
+ *
+ * If the given operator is not cross-type, the result should be the same
+ * operator, but in cross-type situations it is different.
+ *
+ * Returns InvalidOid if no compatible operator can be found.  (This indicates
+ * that the operator should not have been marked oprcanhash.)
+ */
+Oid
+get_compatible_hash_operator(Oid opno, bool use_lhs_type)
+{
+	Oid			result = InvalidOid;
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * Search pg_amop to see if the target operator is registered as the "="
+	 * operator of any hash opfamily.  If the operator is registered in
+	 * multiple opfamilies, assume we can use any one.
+	 */
+	catlist = SearchSysCacheList(AMOPOPID, 1,
+								 ObjectIdGetDatum(opno),
+								 0, 0, 0);
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == HASH_AM_OID &&
+			aform->amopstrategy == HTEqualStrategyNumber)
+		{
+			/* Found a suitable opfamily, get matching single-type operator */
+			Oid		typid;
+
+			/* No extra lookup needed if given operator is single-type */
+			if (aform->amoplefttype == aform->amoprighttype)
+			{
+				result = opno;
+				break;
+			}
+			typid = use_lhs_type ? aform->amoplefttype : aform->amoprighttype;
+			result = get_opfamily_member(aform->amopfamily,
+										 typid, typid,
+										 HTEqualStrategyNumber);
+			if (OidIsValid(result))
+				break;
+			/* failure probably shouldn't happen, but keep looking if so */
+		}
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_op_hash_function
  *		Get the OID of the datatype-specific hash function associated with
@@ -485,6 +656,45 @@ get_op_btree_interpretation(Oid opno, List **opfamilies, List **opstrats)
 	ReleaseSysCacheList(catlist);
 }
 
+/*
+ * ops_in_same_btree_opfamily
+ *		Return TRUE if there exists a btree opfamily containing both operators.
+ *		(This implies that they have compatible notions of equality.)
+ */
+bool
+ops_in_same_btree_opfamily(Oid opno1, Oid opno2)
+{
+	bool		result = false;
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * We search through all the pg_amop entries for opno1.
+	 */
+	catlist = SearchSysCacheList(AMOPOPID, 1,
+								 ObjectIdGetDatum(opno1),
+								 0, 0, 0);
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	op_tuple = &catlist->members[i]->tuple;
+		Form_pg_amop op_form = (Form_pg_amop) GETSTRUCT(op_tuple);
+
+		/* must be btree */
+		if (op_form->amopmethod != BTREE_AM_OID)
+			continue;
+
+		if (op_in_opfamily(opno2, op_form->amopfamily))
+		{
+			result = true;
+			break;
+		}
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 
 /*				---------- AMPROC CACHES ----------						 */
 
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 63c2fec28eae45b613f082966a7cb923cc97874a..76c59a423821363adfdb97324814d4c74d4f0191 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -91,7 +91,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/sort/tuplesort.c,v 1.73 2007/01/09 02:14:15 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/sort/tuplesort.c,v 1.74 2007/01/10 18:06:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -550,8 +550,8 @@ tuplesort_begin_heap(TupleDesc tupDesc,
 		AssertArg(attNums[i] != 0);
 		AssertArg(sortOperators[i] != 0);
 
-		if (!get_op_compare_function(sortOperators[i],
-									 &sortFunction, &reverse))
+		if (!get_compare_function_for_ordering_op(sortOperators[i],
+												  &sortFunction, &reverse))
 			elog(ERROR, "operator %u is not a valid ordering operator",
 				 sortOperators[i]);
 
@@ -643,8 +643,8 @@ tuplesort_begin_datum(Oid datumType,
 	state->datumType = datumType;
 
 	/* lookup the ordering function */
-	if (!get_op_compare_function(sortOperator,
-								 &sortFunction, &reverse))
+	if (!get_compare_function_for_ordering_op(sortOperator,
+											  &sortFunction, &reverse))
 		elog(ERROR, "operator %u is not a valid ordering operator",
 			 sortOperator);
 	fmgr_info(sortFunction, &state->sortOpFn);
@@ -2106,8 +2106,8 @@ SelectSortFunction(Oid sortOperator,
 {
 	bool	reverse;
 
-	if (!get_op_compare_function(sortOperator,
-								 sortFunction, &reverse))
+	if (!get_compare_function_for_ordering_op(sortOperator,
+											  sortFunction, &reverse))
 		elog(ERROR, "operator %u is not a valid ordering operator",
 			 sortOperator);
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ff537e7ddee58005b3d44844010df480ad4fe3da..6e099374cff9d84a552f491ae92b861b67a45977 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.133 2007/01/05 22:19:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.134 2007/01/10 18:06:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -84,14 +84,12 @@ extern bool execTuplesUnequal(TupleTableSlot *slot1,
 				  AttrNumber *matchColIdx,
 				  FmgrInfo *eqfunctions,
 				  MemoryContext evalContext);
-extern FmgrInfo *execTuplesMatchPrepare(TupleDesc tupdesc,
-					   int numCols,
-					   AttrNumber *matchColIdx);
-extern void execTuplesHashPrepare(TupleDesc tupdesc,
-					  int numCols,
-					  AttrNumber *matchColIdx,
-					  FmgrInfo **eqfunctions,
-					  FmgrInfo **hashfunctions);
+extern FmgrInfo *execTuplesMatchPrepare(int numCols,
+					   Oid *eqOperators);
+extern void execTuplesHashPrepare(int numCols,
+					  Oid *eqOperators,
+					  FmgrInfo **eqFunctions,
+					  FmgrInfo **hashFunctions);
 extern TupleHashTable BuildTupleHashTable(int numCols, AttrNumber *keyColIdx,
 					FmgrInfo *eqfunctions,
 					FmgrInfo *hashfunctions,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 44002a9d45db3016c3653f32200f37916aca317d..c9aa190b33dc6b260fd5fec6965292cfaa3a4fc3 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.88 2007/01/09 02:14:15 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.89 2007/01/10 18:06:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -346,14 +346,23 @@ typedef struct NestLoop
 
 /* ----------------
  *		merge join node
+ *
+ * The expected ordering of each mergeable column is described by a btree
+ * opfamily OID, a direction (BTLessStrategyNumber or BTGreaterStrategyNumber)
+ * and a nulls-first flag.  Note that the two sides of each mergeclause may
+ * be of different datatypes, but they are ordered the same way according to
+ * the common opfamily.  The operator in each mergeclause must be an equality
+ * operator of the indicated opfamily.
  * ----------------
  */
 typedef struct MergeJoin
 {
 	Join		join;
 	List	   *mergeclauses;		/* mergeclauses as expression trees */
-	List	   *mergefamilies;		/* OID list of btree opfamilies */
-	List	   *mergestrategies;	/* integer list of btree strategies */
+	/* these are arrays, but have the same length as the mergeclauses list: */
+	Oid		   *mergeFamilies;		/* per-clause OIDs of btree opfamilies */
+	int		   *mergeStrategies;	/* per-clause ordering (ASC or DESC) */
+	bool	   *mergeNullsFirst;	/* per-clause nulls ordering */
 } MergeJoin;
 
 /* ----------------
@@ -399,6 +408,7 @@ typedef struct Group
 	Plan		plan;
 	int			numCols;		/* number of grouping columns */
 	AttrNumber *grpColIdx;		/* their indexes in the target list */
+	Oid		   *grpOperators;	/* equality operators to compare with */
 } Group;
 
 /* ---------------
@@ -428,6 +438,7 @@ typedef struct Agg
 	AggStrategy aggstrategy;
 	int			numCols;		/* number of grouping columns */
 	AttrNumber *grpColIdx;		/* their indexes in the target list */
+	Oid		   *grpOperators;	/* equality operators to compare with */
 	long		numGroups;		/* estimated number of groups in input */
 } Agg;
 
@@ -439,7 +450,8 @@ typedef struct Unique
 {
 	Plan		plan;
 	int			numCols;		/* number of columns to check for uniqueness */
-	AttrNumber *uniqColIdx;		/* indexes into the target list */
+	AttrNumber *uniqColIdx;		/* their indexes in the target list */
+	Oid		   *uniqOperators;	/* equality operators to compare with */
 } Unique;
 
 /* ----------------
@@ -470,8 +482,9 @@ typedef struct SetOp
 	SetOpCmd	cmd;			/* what to do */
 	int			numCols;		/* number of columns to check for
 								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* indexes into the target list */
-	AttrNumber	flagColIdx;
+	AttrNumber *dupColIdx;		/* their indexes in the target list */
+	Oid		   *dupOperators;	/* equality operators to compare with */
+	AttrNumber	flagColIdx;		/* where is the flag column, if any */
 } SetOp;
 
 /* ----------------
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 4e285a765ad72f3ad916e6f7f537fba612b27319..f7900377398cc68b9acd764c73bf56a00089c4f9 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.131 2007/01/09 02:14:15 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.132 2007/01/10 18:06:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -618,9 +618,11 @@ typedef JoinPath NestPath;
  * A mergejoin path has these fields.
  *
  * path_mergeclauses lists the clauses (in the form of RestrictInfos)
- * that will be used in the merge.  The parallel lists path_mergefamilies
- * and path_mergestrategies specify the merge semantics for each clause
- * (in effect, defining the relevant sort ordering for each clause).
+ * that will be used in the merge.  The parallel arrays path_mergeFamilies,
+ * path_mergeStrategies, and path_mergeNullsFirst specify the merge semantics
+ * for each clause (i.e., define the relevant sort ordering for each clause).
+ * (XXX is this the most reasonable path-time representation?  It's at least
+ * partially redundant with the pathkeys of the input paths.)
  *
  * Note that the mergeclauses are a subset of the parent relation's
  * restriction-clause list.  Any join clauses that are not mergejoinable
@@ -637,8 +639,10 @@ typedef struct MergePath
 {
 	JoinPath	jpath;
 	List	   *path_mergeclauses;		/* join clauses to be used for merge */
-	List	   *path_mergefamilies;		/* OID list of btree opfamilies */
-	List	   *path_mergestrategies;	/* integer list of btree strategies */
+	/* these are arrays, but have the same length as the mergeclauses list: */
+	Oid		   *path_mergeFamilies;		/* per-clause OIDs of opfamilies */
+	int		   *path_mergeStrategies;	/* per-clause ordering (ASC or DESC) */
+	bool	   *path_mergeNullsFirst;	/* per-clause nulls ordering */
 	List	   *outersortkeys;	/* keys for explicit sort, if any */
 	List	   *innersortkeys;	/* keys for explicit sort, if any */
 } MergePath;
@@ -885,6 +889,9 @@ typedef struct OuterJoinInfo
  * the order of joining and use special join methods at some join points.
  * We record information about each such IN clause in an InClauseInfo struct.
  * These structs are kept in the PlannerInfo node's in_info_list.
+ *
+ * Note: sub_targetlist is just a list of Vars or expressions; it does not
+ * contain TargetEntry nodes.
  */
 
 typedef struct InClauseInfo
@@ -893,11 +900,7 @@ typedef struct InClauseInfo
 	Relids		lefthand;		/* base relids in lefthand expressions */
 	Relids		righthand;		/* base relids coming from the subselect */
 	List	   *sub_targetlist; /* targetlist of original RHS subquery */
-
-	/*
-	 * Note: sub_targetlist is just a list of Vars or expressions; it does not
-	 * contain TargetEntry nodes.
-	 */
+	List	   *in_operators;	/* OIDs of the IN's equality operator(s) */
 } InClauseInfo;
 
 /*
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index b1e505cd6aec00f3065852a9a255e582d81dcd55..fd5c78372ecddd27ce1db3d1058e45bd26c243cd 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.74 2007/01/05 22:19:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.75 2007/01/10 18:06:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -71,8 +71,9 @@ extern MergePath *create_mergejoin_path(PlannerInfo *root,
 					  List *restrict_clauses,
 					  List *pathkeys,
 					  List *mergeclauses,
-					  List *mergefamilies,
-					  List *mergestrategies,
+					  Oid *mergefamilies,
+					  int *mergestrategies,
+					  bool *mergenullsfirst,
 					  List *outersortkeys,
 					  List *innersortkeys);
 
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index cb6ed70c0c54205e207c85d7fade6a881eed5e3e..0f6799338bbaa3bd9d84996e67b73cb524bf2945 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.96 2007/01/05 22:19:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.97 2007/01/10 18:06:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,11 +44,11 @@ extern Sort *make_sort_from_groupcols(PlannerInfo *root, List *groupcls,
 						 AttrNumber *grpColIdx, Plan *lefttree);
 extern Agg *make_agg(PlannerInfo *root, List *tlist, List *qual,
 		 AggStrategy aggstrategy,
-		 int numGroupCols, AttrNumber *grpColIdx,
+		 int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
 		 long numGroups, int numAggs,
 		 Plan *lefttree);
 extern Group *make_group(PlannerInfo *root, List *tlist, List *qual,
-		   int numGroupCols, AttrNumber *grpColIdx,
+		   int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
 		   double numGroups,
 		   Plan *lefttree);
 extern Material *make_material(Plan *lefttree);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 15d2b8a06d9d64ad175b31b071a11468ab101d09..8d4c0d8e3ef6d30a62c268bdbfa9b1a6fdcb6502 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.111 2007/01/09 02:14:16 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.112 2007/01/10 18:06:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,10 +37,15 @@ extern Oid	get_opfamily_member(Oid opfamily, Oid lefttype, Oid righttype,
 								int16 strategy);
 extern bool get_op_mergejoin_info(Oid eq_op, Oid *left_sortop,
 					  Oid *right_sortop, Oid *opfamily);
-extern bool get_op_compare_function(Oid opno, Oid *cmpfunc, bool *reverse);
+extern bool get_compare_function_for_ordering_op(Oid opno,
+												 Oid *cmpfunc, bool *reverse);
+extern Oid	get_equality_op_for_ordering_op(Oid opno);
+extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
+extern Oid	get_compatible_hash_operator(Oid opno, bool use_lhs_type);
 extern Oid	get_op_hash_function(Oid opno);
 extern void get_op_btree_interpretation(Oid opno,
 							List **opfamilies, List **opstrats);
+extern bool ops_in_same_btree_opfamily(Oid opno1, Oid opno2);
 extern Oid	get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype,
 							  int16 procnum);
 extern char *get_attname(Oid relid, AttrNumber attnum);