diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 28324361a950f31737c0cbb6909726d02ce1af5f..51f847c9f9777707756a823e1f76ef0275bceed7 100644
*** a/doc/src/sgml/xindex.sgml
--- b/doc/src/sgml/xindex.sgml
*************** DEFAULT FOR TYPE int4 USING btree FAMILY
*** 758,764 ****
    OPERATOR 3 = ,
    OPERATOR 4 >= ,
    OPERATOR 5 > ,
!   FUNCTION 1 btint4cmp(int4, int4) ;
  
  CREATE OPERATOR CLASS int2_ops
  DEFAULT FOR TYPE int2 USING btree FAMILY integer_ops AS
--- 758,765 ----
    OPERATOR 3 = ,
    OPERATOR 4 >= ,
    OPERATOR 5 > ,
!   FUNCTION 1 btint4cmp(int4, int4) ,
!   FUNCTION 2 btint4sortsupport(internal) ;
  
  CREATE OPERATOR CLASS int2_ops
  DEFAULT FOR TYPE int2 USING btree FAMILY integer_ops AS
diff --git a/src/backend/access/nbtree/nbtcompare.c b/src/backend/access/nbtree/nbtcompare.c
index 23f2b61fe902cff7eebcf8526cf8116d90be75e8..9e065f71e3a6a21349bc6e43c0bb922ae641a98e 100644
*** a/src/backend/access/nbtree/nbtcompare.c
--- b/src/backend/access/nbtree/nbtcompare.c
***************
*** 49,54 ****
--- 49,55 ----
  #include "postgres.h"
  
  #include "utils/builtins.h"
+ #include "utils/sortsupport.h"
  
  
  Datum
*************** btint4cmp(PG_FUNCTION_ARGS)
*** 83,88 ****
--- 84,112 ----
  		PG_RETURN_INT32(-1);
  }
  
+ static int
+ btint4fastcmp(Datum x, Datum y, SortSupport ssup)
+ {
+ 	int32		a = DatumGetInt32(x);
+ 	int32		b = DatumGetInt32(y);
+ 
+ 	if (a > b)
+ 		return 1;
+ 	else if (a == b)
+ 		return 0;
+ 	else
+ 		return -1;
+ }
+ 
+ Datum
+ btint4sortsupport(PG_FUNCTION_ARGS)
+ {
+ 	SortSupport	ssup = (SortSupport) PG_GETARG_POINTER(0);
+ 
+ 	ssup->comparator = btint4fastcmp;
+ 	PG_RETURN_VOID();
+ }
+ 
  Datum
  btint8cmp(PG_FUNCTION_ARGS)
  {
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 314324618a8216e7adc1eb2c18175f9a6b2ae5dd..c3d3958da7628ca47493e53e3e18e087a93cbd70 100644
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 47,54 ****
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/pg_rusage.h"
  #include "utils/syscache.h"
- #include "utils/tuplesort.h"
  #include "utils/timestamp.h"
  #include "utils/tqual.h"
  
--- 47,54 ----
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/pg_rusage.h"
+ #include "utils/sortsupport.h"
  #include "utils/syscache.h"
  #include "utils/timestamp.h"
  #include "utils/tqual.h"
  
*************** typedef struct
*** 1774,1781 ****
  
  typedef struct
  {
! 	FmgrInfo   *cmpFn;
! 	int			cmpFlags;
  	int		   *tupnoLink;
  } CompareScalarsContext;
  
--- 1774,1780 ----
  
  typedef struct
  {
! 	SortSupport ssup;
  	int		   *tupnoLink;
  } CompareScalarsContext;
  
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2222,2230 ****
  	bool		is_varwidth = (!stats->attrtype->typbyval &&
  							   stats->attrtype->typlen < 0);
  	double		corr_xysum;
! 	Oid			cmpFn;
! 	int			cmpFlags;
! 	FmgrInfo	f_cmpfn;
  	ScalarItem *values;
  	int			values_cnt = 0;
  	int		   *tupnoLink;
--- 2221,2227 ----
  	bool		is_varwidth = (!stats->attrtype->typbyval &&
  							   stats->attrtype->typlen < 0);
  	double		corr_xysum;
! 	SortSupportData ssup;
  	ScalarItem *values;
  	int			values_cnt = 0;
  	int		   *tupnoLink;
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2238,2245 ****
  	tupnoLink = (int *) palloc(samplerows * sizeof(int));
  	track = (ScalarMCVItem *) palloc(num_mcv * sizeof(ScalarMCVItem));
  
! 	SelectSortFunction(mystats->ltopr, false, &cmpFn, &cmpFlags);
! 	fmgr_info(cmpFn, &f_cmpfn);
  
  	/* Initial scan to find sortable values */
  	for (i = 0; i < samplerows; i++)
--- 2235,2247 ----
  	tupnoLink = (int *) palloc(samplerows * sizeof(int));
  	track = (ScalarMCVItem *) palloc(num_mcv * sizeof(ScalarMCVItem));
  
! 	memset(&ssup, 0, sizeof(ssup));
! 	ssup.ssup_cxt = CurrentMemoryContext;
! 	/* We always use the default collation for statistics */
! 	ssup.ssup_collation = DEFAULT_COLLATION_OID;
! 	ssup.ssup_nulls_first = false;
! 
! 	PrepareSortSupportFromOrderingOp(mystats->ltopr, &ssup);
  
  	/* Initial scan to find sortable values */
  	for (i = 0; i < samplerows; i++)
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2307,2314 ****
  		CompareScalarsContext cxt;
  
  		/* Sort the collected values */
! 		cxt.cmpFn = &f_cmpfn;
! 		cxt.cmpFlags = cmpFlags;
  		cxt.tupnoLink = tupnoLink;
  		qsort_arg((void *) values, values_cnt, sizeof(ScalarItem),
  				  compare_scalars, (void *) &cxt);
--- 2309,2315 ----
  		CompareScalarsContext cxt;
  
  		/* Sort the collected values */
! 		cxt.ssup = &ssup;
  		cxt.tupnoLink = tupnoLink;
  		qsort_arg((void *) values, values_cnt, sizeof(ScalarItem),
  				  compare_scalars, (void *) &cxt);
*************** compare_scalars(const void *a, const voi
*** 2712,2723 ****
  	Datum		db = ((const ScalarItem *) b)->value;
  	int			tb = ((const ScalarItem *) b)->tupno;
  	CompareScalarsContext *cxt = (CompareScalarsContext *) arg;
! 	int32		compare;
  
! 	/* We always use the default collation for statistics */
! 	compare = ApplySortFunction(cxt->cmpFn, cxt->cmpFlags,
! 								DEFAULT_COLLATION_OID,
! 								da, false, db, false);
  	if (compare != 0)
  		return compare;
  
--- 2713,2721 ----
  	Datum		db = ((const ScalarItem *) b)->value;
  	int			tb = ((const ScalarItem *) b)->tupno;
  	CompareScalarsContext *cxt = (CompareScalarsContext *) arg;
! 	int			compare;
  
! 	compare = ApplySortComparator(da, false, db, false, cxt->ssup);
  	if (compare != 0)
  		return compare;
  
diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c
index 43059664b9348ae462dcb6b1166124f4f0fde2ee..6065e2176ac118e135583fb1f0cdf72bb5f22252 100644
*** a/src/backend/executor/nodeMergeAppend.c
--- b/src/backend/executor/nodeMergeAppend.c
***************
*** 38,47 ****
  
  #include "postgres.h"
  
- #include "access/nbtree.h"
  #include "executor/execdebug.h"
  #include "executor/nodeMergeAppend.h"
- #include "utils/lsyscache.h"
  
  /*
   * It gets quite confusing having a heap array (indexed by integers) which
--- 38,45 ----
*************** ExecInitMergeAppend(MergeAppend *node, E
*** 128,165 ****
  	 * initialize sort-key information
  	 */
  	mergestate->ms_nkeys = node->numCols;
! 	mergestate->ms_scankeys = palloc0(sizeof(ScanKeyData) * node->numCols);
  
  	for (i = 0; i < node->numCols; i++)
  	{
! 		Oid			sortFunction;
! 		bool		reverse;
! 		int			flags;
! 
! 		if (!get_compare_function_for_ordering_op(node->sortOperators[i],
! 												  &sortFunction, &reverse))
! 			elog(ERROR, "operator %u is not a valid ordering operator",
! 				 node->sortOperators[i]);
  
! 		/* We use btree's conventions for encoding directionality */
! 		flags = 0;
! 		if (reverse)
! 			flags |= SK_BT_DESC;
! 		if (node->nullsFirst[i])
! 			flags |= SK_BT_NULLS_FIRST;
  
! 		/*
! 		 * We needn't fill in sk_strategy or sk_subtype since these scankeys
! 		 * will never be passed to an index.
! 		 */
! 		ScanKeyEntryInitialize(&mergestate->ms_scankeys[i],
! 							   flags,
! 							   node->sortColIdx[i],
! 							   InvalidStrategy,
! 							   InvalidOid,
! 							   node->collations[i],
! 							   sortFunction,
! 							   (Datum) 0);
  	}
  
  	/*
--- 126,143 ----
  	 * initialize sort-key information
  	 */
  	mergestate->ms_nkeys = node->numCols;
! 	mergestate->ms_sortkeys = palloc0(sizeof(SortSupportData) * node->numCols);
  
  	for (i = 0; i < node->numCols; i++)
  	{
! 		SortSupport	sortKey = mergestate->ms_sortkeys + i;
  
! 		sortKey->ssup_cxt = CurrentMemoryContext;
! 		sortKey->ssup_collation = node->collations[i];
! 		sortKey->ssup_nulls_first = node->nullsFirst[i];
! 		sortKey->ssup_attno = node->sortColIdx[i];
  
! 		PrepareSortSupportFromOrderingOp(node->sortOperators[i], sortKey);
  	}
  
  	/*
*************** heap_compare_slots(MergeAppendState *nod
*** 298,342 ****
  
  	for (nkey = 0; nkey < node->ms_nkeys; nkey++)
  	{
! 		ScanKey		scankey = node->ms_scankeys + nkey;
! 		AttrNumber	attno = scankey->sk_attno;
  		Datum		datum1,
  					datum2;
  		bool		isNull1,
  					isNull2;
! 		int32		compare;
  
  		datum1 = slot_getattr(s1, attno, &isNull1);
  		datum2 = slot_getattr(s2, attno, &isNull2);
  
! 		if (isNull1)
! 		{
! 			if (isNull2)
! 				continue;		/* NULL "=" NULL */
! 			else if (scankey->sk_flags & SK_BT_NULLS_FIRST)
! 				return -1;		/* NULL "<" NOT_NULL */
! 			else
! 				return 1;		/* NULL ">" NOT_NULL */
! 		}
! 		else if (isNull2)
! 		{
! 			if (scankey->sk_flags & SK_BT_NULLS_FIRST)
! 				return 1;		/* NOT_NULL ">" NULL */
! 			else
! 				return -1;		/* NOT_NULL "<" NULL */
! 		}
! 		else
! 		{
! 			compare = DatumGetInt32(FunctionCall2Coll(&scankey->sk_func,
! 													  scankey->sk_collation,
! 													  datum1, datum2));
! 			if (compare != 0)
! 			{
! 				if (scankey->sk_flags & SK_BT_DESC)
! 					compare = -compare;
! 				return compare;
! 			}
! 		}
  	}
  	return 0;
  }
--- 276,297 ----
  
  	for (nkey = 0; nkey < node->ms_nkeys; nkey++)
  	{
! 		SortSupport	sortKey = node->ms_sortkeys + nkey;
! 		AttrNumber	attno = sortKey->ssup_attno;
  		Datum		datum1,
  					datum2;
  		bool		isNull1,
  					isNull2;
! 		int			compare;
  
  		datum1 = slot_getattr(s1, attno, &isNull1);
  		datum2 = slot_getattr(s2, attno, &isNull2);
  
! 		compare = ApplySortComparator(datum1, isNull1,
! 									  datum2, isNull2,
! 									  sortKey);
! 		if (compare != 0)
! 			return compare;
  	}
  	return 0;
  }
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index deaa79ed9fbddfa5397887439085f499a5981259..634931da4251056d1b6762144b03b42d4a47573d 100644
*** a/src/backend/executor/nodeMergejoin.c
--- b/src/backend/executor/nodeMergejoin.c
***************
*** 95,102 ****
  #include "access/nbtree.h"
  #include "executor/execdebug.h"
  #include "executor/nodeMergejoin.h"
- #include "miscadmin.h"
- #include "utils/acl.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  
--- 95,100 ----
*************** typedef struct MergeJoinClauseData
*** 135,147 ****
  	bool		risnull;
  
  	/*
! 	 * The comparison strategy in use, and the lookup info to let us call the
! 	 * btree comparison support function, and the collation to use.
  	 */
! 	bool		reverse;		/* if true, negate the cmpfn's output */
! 	bool		nulls_first;	/* if true, nulls sort low */
! 	FmgrInfo	cmpfinfo;
! 	Oid			collation;
  }	MergeJoinClauseData;
  
  /* Result type for MJEvalOuterValues and MJEvalInnerValues */
--- 133,142 ----
  	bool		risnull;
  
  	/*
! 	 * Everything we need to know to compare the left and right values is
! 	 * stored here.
  	 */
! 	SortSupportData ssup;
  }	MergeJoinClauseData;
  
  /* Result type for MJEvalOuterValues and MJEvalInnerValues */
*************** MJExamineQuals(List *mergeclauses,
*** 203,210 ****
  		int			op_strategy;
  		Oid			op_lefttype;
  		Oid			op_righttype;
! 		RegProcedure cmpproc;
! 		AclResult	aclresult;
  
  		if (!IsA(qual, OpExpr))
  			elog(ERROR, "mergejoin clause is not an OpExpr");
--- 198,204 ----
  		int			op_strategy;
  		Oid			op_lefttype;
  		Oid			op_righttype;
! 		Oid			sortfunc;
  
  		if (!IsA(qual, OpExpr))
  			elog(ERROR, "mergejoin clause is not an OpExpr");
*************** MJExamineQuals(List *mergeclauses,
*** 215,220 ****
--- 209,225 ----
  		clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent);
  		clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent);
  
+ 		/* Set up sort support data */
+ 		clause->ssup.ssup_cxt = CurrentMemoryContext;
+ 		clause->ssup.ssup_collation = collation;
+ 		if (opstrategy == BTLessStrategyNumber)
+ 			clause->ssup.ssup_reverse = false;
+ 		else if (opstrategy == BTGreaterStrategyNumber)
+ 			clause->ssup.ssup_reverse = true;
+ 		else	/* planner screwed up */
+ 			elog(ERROR, "unsupported mergejoin strategy %d", opstrategy);
+ 		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,
*************** MJExamineQuals(List *mergeclauses,
*** 224,259 ****
  			elog(ERROR, "cannot merge using non-equality operator %u",
  				 qual->opno);
  
! 		/* And get the matching support procedure (comparison function) */
! 		cmpproc = get_opfamily_proc(opfamily,
! 									op_lefttype,
! 									op_righttype,
! 									BTORDER_PROC);
! 		if (!RegProcedureIsValid(cmpproc))		/* should not happen */
! 			elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
! 				 BTORDER_PROC, op_lefttype, op_righttype, opfamily);
! 
! 		/* Check permission to call cmp function */
! 		aclresult = pg_proc_aclcheck(cmpproc, GetUserId(), ACL_EXECUTE);
! 		if (aclresult != ACLCHECK_OK)
! 			aclcheck_error(aclresult, ACL_KIND_PROC,
! 						   get_func_name(cmpproc));
! 
! 		/* Set up the fmgr lookup information */
! 		fmgr_info(cmpproc, &(clause->cmpfinfo));
! 
! 		/* Fill the additional comparison-strategy flags */
! 		if (opstrategy == BTLessStrategyNumber)
! 			clause->reverse = false;
! 		else if (opstrategy == BTGreaterStrategyNumber)
! 			clause->reverse = true;
! 		else	/* planner screwed up */
! 			elog(ERROR, "unsupported mergejoin strategy %d", opstrategy);
! 
! 		clause->nulls_first = nulls_first;
! 
! 		/* ... and the collation too */
! 		clause->collation = collation;
  
  		iClause++;
  	}
--- 229,258 ----
  			elog(ERROR, "cannot merge using non-equality operator %u",
  				 qual->opno);
  
! 		/* And get the matching support or comparison function */
! 		sortfunc = get_opfamily_proc(opfamily,
! 									 op_lefttype,
! 									 op_righttype,
! 									 BTSORTSUPPORT_PROC);
! 		if (OidIsValid(sortfunc))
! 		{
! 			/* The sort support function should provide a comparator */
! 			OidFunctionCall1(sortfunc, PointerGetDatum(&clause->ssup));
! 			Assert(clause->ssup.comparator != NULL);
! 		}
! 		else
! 		{
! 			/* opfamily doesn't provide sort support, get comparison func */
! 			sortfunc = get_opfamily_proc(opfamily,
! 										 op_lefttype,
! 										 op_righttype,
! 										 BTORDER_PROC);
! 			if (!OidIsValid(sortfunc))		/* should not happen */
! 				elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
! 					 BTORDER_PROC, op_lefttype, op_righttype, opfamily);
! 			/* We'll use a shim to call the old-style btree comparator */
! 			PrepareSortSupportComparisonShim(sortfunc, &clause->ssup);
! 		}
  
  		iClause++;
  	}
*************** MJEvalOuterValues(MergeJoinState *merges
*** 310,316 ****
  		if (clause->lisnull)
  		{
  			/* match is impossible; can we end the join early? */
! 			if (i == 0 && !clause->nulls_first && !mergestate->mj_FillOuter)
  				result = MJEVAL_ENDOFJOIN;
  			else if (result == MJEVAL_MATCHABLE)
  				result = MJEVAL_NONMATCHABLE;
--- 309,316 ----
  		if (clause->lisnull)
  		{
  			/* match is impossible; can we end the join early? */
! 			if (i == 0 && !clause->ssup.ssup_nulls_first &&
! 				!mergestate->mj_FillOuter)
  				result = MJEVAL_ENDOFJOIN;
  			else if (result == MJEVAL_MATCHABLE)
  				result = MJEVAL_NONMATCHABLE;
*************** MJEvalInnerValues(MergeJoinState *merges
*** 356,362 ****
  		if (clause->risnull)
  		{
  			/* match is impossible; can we end the join early? */
! 			if (i == 0 && !clause->nulls_first && !mergestate->mj_FillInner)
  				result = MJEVAL_ENDOFJOIN;
  			else if (result == MJEVAL_MATCHABLE)
  				result = MJEVAL_NONMATCHABLE;
--- 356,363 ----
  		if (clause->risnull)
  		{
  			/* match is impossible; can we end the join early? */
! 			if (i == 0 && !clause->ssup.ssup_nulls_first &&
! 				!mergestate->mj_FillInner)
  				result = MJEVAL_ENDOFJOIN;
  			else if (result == MJEVAL_MATCHABLE)
  				result = MJEVAL_NONMATCHABLE;
*************** MJEvalInnerValues(MergeJoinState *merges
*** 373,392 ****
   *
   * Compare the mergejoinable values of the current two input tuples
   * and return 0 if they are equal (ie, the mergejoin equalities all
!  * succeed), +1 if outer > inner, -1 if outer < inner.
   *
   * MJEvalOuterValues and MJEvalInnerValues must already have been called
   * for the current outer and inner tuples, respectively.
   */
! static int32
  MJCompare(MergeJoinState *mergestate)
  {
! 	int32		result = 0;
  	bool		nulleqnull = false;
  	ExprContext *econtext = mergestate->js.ps.ps_ExprContext;
  	int			i;
  	MemoryContext oldContext;
- 	FunctionCallInfoData fcinfo;
  
  	/*
  	 * Call the comparison functions in short-lived context, in case they leak
--- 374,392 ----
   *
   * 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.
   *
   * MJEvalOuterValues and MJEvalInnerValues must already have been called
   * for the current outer and inner tuples, respectively.
   */
! static int
  MJCompare(MergeJoinState *mergestate)
  {
! 	int			result = 0;
  	bool		nulleqnull = false;
  	ExprContext *econtext = mergestate->js.ps.ps_ExprContext;
  	int			i;
  	MemoryContext oldContext;
  
  	/*
  	 * Call the comparison functions in short-lived context, in case they leak
*************** MJCompare(MergeJoinState *mergestate)
*** 399,460 ****
  	for (i = 0; i < mergestate->mj_NumClauses; i++)
  	{
  		MergeJoinClause clause = &mergestate->mj_Clauses[i];
- 		Datum		fresult;
  
  		/*
! 		 * Deal with null inputs.
! 		 */
! 		if (clause->lisnull)
! 		{
! 			if (clause->risnull)
! 			{
! 				nulleqnull = true;		/* NULL "=" NULL */
! 				continue;
! 			}
! 			if (clause->nulls_first)
! 				result = -1;	/* NULL "<" NOT_NULL */
! 			else
! 				result = 1;		/* NULL ">" NOT_NULL */
! 			break;
! 		}
! 		if (clause->risnull)
! 		{
! 			if (clause->nulls_first)
! 				result = 1;		/* NOT_NULL ">" NULL */
! 			else
! 				result = -1;	/* NOT_NULL "<" NULL */
! 			break;
! 		}
! 
! 		/*
! 		 * OK to call the comparison function.
  		 */
! 		InitFunctionCallInfoData(fcinfo, &(clause->cmpfinfo), 2,
! 								 clause->collation, NULL, NULL);
! 		fcinfo.arg[0] = clause->ldatum;
! 		fcinfo.arg[1] = clause->rdatum;
! 		fcinfo.argnull[0] = false;
! 		fcinfo.argnull[1] = false;
! 		fresult = FunctionCallInvoke(&fcinfo);
! 		if (fcinfo.isnull)
  		{
! 			nulleqnull = true;	/* treat like NULL = NULL */
  			continue;
  		}
- 		result = DatumGetInt32(fresult);
  
! 		if (clause->reverse)
! 			result = -result;
  
  		if (result != 0)
  			break;
  	}
  
  	/*
! 	 * If we had any null comparison results or NULL-vs-NULL inputs, we do not
! 	 * want to report that the tuples are equal.  Instead, if result is still
! 	 * 0, change it to +1.	This will result in advancing the inner side of
! 	 * the join.
  	 *
  	 * Likewise, if there was a constant-false joinqual, do not report
  	 * equality.  We have to check this as part of the mergequals, else the
--- 399,426 ----
  	for (i = 0; i < mergestate->mj_NumClauses; i++)
  	{
  		MergeJoinClause clause = &mergestate->mj_Clauses[i];
  
  		/*
! 		 * Special case for NULL-vs-NULL, else use standard comparison.
  		 */
! 		if (clause->lisnull && clause->risnull)
  		{
! 			nulleqnull = true;		/* NULL "=" NULL */
  			continue;
  		}
  
! 		result = ApplySortComparator(clause->ldatum, clause->lisnull,
! 									 clause->rdatum, clause->risnull,
! 									 &clause->ssup);
  
  		if (result != 0)
  			break;
  	}
  
  	/*
! 	 * If we had any NULL-vs-NULL inputs, we do not want to report that the
! 	 * tuples are equal.  Instead, if result is still 0, change it to +1.
! 	 * This will result in advancing the inner side of the join.
  	 *
  	 * Likewise, if there was a constant-false joinqual, do not report
  	 * equality.  We have to check this as part of the mergequals, else the
*************** ExecMergeJoin(MergeJoinState *node)
*** 647,653 ****
  	List	   *joinqual;
  	List	   *otherqual;
  	bool		qualResult;
! 	int32		compareResult;
  	PlanState  *innerPlan;
  	TupleTableSlot *innerTupleSlot;
  	PlanState  *outerPlan;
--- 613,619 ----
  	List	   *joinqual;
  	List	   *otherqual;
  	bool		qualResult;
! 	int			compareResult;
  	PlanState  *innerPlan;
  	TupleTableSlot *innerTupleSlot;
  	PlanState  *outerPlan;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index cb341b8db679382c3ab8de31ff7a0bb57bc8dc50..7a4306e93f5545764cbd02e6aa042120b385790c 100644
*** a/src/backend/utils/cache/lsyscache.c
--- b/src/backend/utils/cache/lsyscache.c
*************** get_ordering_op_properties(Oid opno,
*** 244,262 ****
  }
  
  /*
!  * get_compare_function_for_ordering_op
!  *		Get the OID of the datatype-specific btree comparison function
   *		associated with an ordering operator (a "<" or ">" operator).
   *
!  * *cmpfunc receives the comparison function OID.
   * *reverse is set FALSE if the operator is "<", TRUE if it's ">"
!  * (indicating the comparison result must be negated before use).
   *
   * Returns TRUE if successful, FALSE if no btree function can be found.
   * (This indicates that the operator is not a valid ordering operator.)
   */
  bool
! get_compare_function_for_ordering_op(Oid opno, Oid *cmpfunc, bool *reverse)
  {
  	Oid			opfamily;
  	Oid			opcintype;
--- 244,265 ----
  }
  
  /*
!  * get_sort_function_for_ordering_op
!  *		Get the OID of the datatype-specific btree sort support function,
!  *		or if there is none, the btree comparison function,
   *		associated with an ordering operator (a "<" or ">" operator).
   *
!  * *sortfunc receives the support or comparison function OID.
!  * *issupport is set TRUE if it's a support func, FALSE if a comparison func.
   * *reverse is set FALSE if the operator is "<", TRUE if it's ">"
!  * (indicating that comparison results must be negated before use).
   *
   * Returns TRUE if successful, FALSE if no btree function can be found.
   * (This indicates that the operator is not a valid ordering operator.)
   */
  bool
! get_sort_function_for_ordering_op(Oid opno, Oid *sortfunc,
! 								  bool *issupport, bool *reverse)
  {
  	Oid			opfamily;
  	Oid			opcintype;
*************** get_compare_function_for_ordering_op(Oid
*** 267,287 ****
  								   &opfamily, &opcintype, &strategy))
  	{
  		/* Found a suitable opfamily, get matching support function */
! 		*cmpfunc = get_opfamily_proc(opfamily,
! 									 opcintype,
! 									 opcintype,
! 									 BTORDER_PROC);
! 
! 		if (!OidIsValid(*cmpfunc))		/* should not happen */
! 			elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
! 				 BTORDER_PROC, opcintype, opcintype, opfamily);
  		*reverse = (strategy == BTGreaterStrategyNumber);
  		return true;
  	}
  
  	/* ensure outputs are set on failure */
! 	*cmpfunc = InvalidOid;
! 
  	*reverse = false;
  	return false;
  }
--- 270,300 ----
  								   &opfamily, &opcintype, &strategy))
  	{
  		/* Found a suitable opfamily, get matching support function */
! 		*sortfunc = get_opfamily_proc(opfamily,
! 									  opcintype,
! 									  opcintype,
! 									  BTSORTSUPPORT_PROC);
! 		if (OidIsValid(*sortfunc))
! 			*issupport = true;
! 		else
! 		{
! 			/* opfamily doesn't provide sort support, get comparison func */
! 			*sortfunc = get_opfamily_proc(opfamily,
! 										  opcintype,
! 										  opcintype,
! 										  BTORDER_PROC);
! 			if (!OidIsValid(*sortfunc))		/* should not happen */
! 				elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
! 					 BTORDER_PROC, opcintype, opcintype, opfamily);
! 			*issupport = false;
! 		}
  		*reverse = (strategy == BTGreaterStrategyNumber);
  		return true;
  	}
  
  	/* ensure outputs are set on failure */
! 	*sortfunc = InvalidOid;
! 	*issupport = false;
  	*reverse = false;
  	return false;
  }
diff --git a/src/backend/utils/sort/Makefile b/src/backend/utils/sort/Makefile
index 9e20ecb3267baed7c95b607d8f711a2ed9a3a81b..2ef4965ee6db442f3d2e94903d76a4cdb69db521 100644
*** a/src/backend/utils/sort/Makefile
--- b/src/backend/utils/sort/Makefile
*************** subdir = src/backend/utils/sort
*** 12,17 ****
  top_builddir = ../../../..
  include $(top_builddir)/src/Makefile.global
  
! OBJS = logtape.o tuplesort.o tuplestore.o
  
  include $(top_srcdir)/src/backend/common.mk
--- 12,17 ----
  top_builddir = ../../../..
  include $(top_builddir)/src/Makefile.global
  
! OBJS = logtape.o sortsupport.o tuplesort.o tuplestore.o
  
  include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/sort/sortsupport.c b/src/backend/utils/sort/sortsupport.c
index ...4a87f11827f36e81708a3ee0a9031e04832b24e0 .
*** a/src/backend/utils/sort/sortsupport.c
--- b/src/backend/utils/sort/sortsupport.c
***************
*** 0 ****
--- 1,160 ----
+ /*-------------------------------------------------------------------------
+  *
+  * sortsupport.c
+  *	  Support routines for accelerated sorting.
+  *
+  *
+  * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * IDENTIFICATION
+  *	  src/backend/utils/sort/sortsupport.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres.h"
+ 
+ #include "fmgr.h"
+ #include "utils/lsyscache.h"
+ #include "utils/sortsupport.h"
+ 
+ 
+ /* Info needed to use an old-style comparison function as a sort comparator */
+ typedef struct
+ {
+ 	FunctionCallInfoData fcinfo;	/* reusable callinfo structure */
+ 	FmgrInfo	flinfo;				/* lookup data for comparison function */
+ } SortShimExtra;
+ 
+ 
+ /*
+  * sortsupport.h defines inline versions of these functions if allowed by the
+  * compiler; in which case the definitions below are skipped.
+  */
+ #ifndef USE_INLINE
+ 
+ /*
+  * Apply a sort comparator function and return a 3-way comparison result.
+  * This takes care of handling reverse-sort and NULLs-ordering properly.
+  */
+ int
+ ApplySortComparator(Datum datum1, bool isNull1,
+ 					Datum datum2, bool isNull2,
+ 					SortSupport ssup)
+ {
+ 	int			compare;
+ 
+ 	if (isNull1)
+ 	{
+ 		if (isNull2)
+ 			compare = 0;		/* NULL "=" NULL */
+ 		else if (ssup->ssup_nulls_first)
+ 			compare = -1;		/* NULL "<" NOT_NULL */
+ 		else
+ 			compare = 1;		/* NULL ">" NOT_NULL */
+ 	}
+ 	else if (isNull2)
+ 	{
+ 		if (ssup->ssup_nulls_first)
+ 			compare = 1;		/* NOT_NULL ">" NULL */
+ 		else
+ 			compare = -1;		/* NOT_NULL "<" NULL */
+ 	}
+ 	else
+ 	{
+ 		compare = (*ssup->comparator) (datum1, datum2, ssup);
+ 		if (ssup->ssup_reverse)
+ 			compare = -compare;
+ 	}
+ 
+ 	return compare;
+ }
+ 
+ #endif   /* ! USE_INLINE */
+ 
+ /*
+  * Shim function for calling an old-style comparator
+  *
+  * This is essentially an inlined version of FunctionCall2Coll(), except
+  * we assume that the FunctionCallInfoData was already mostly set up by
+  * PrepareSortSupportComparisonShim.
+  */
+ static int
+ comparison_shim(Datum x, Datum y, SortSupport ssup)
+ {
+ 	SortShimExtra *extra = (SortShimExtra *) ssup->ssup_extra;
+ 	Datum		result;
+ 
+ 	extra->fcinfo.arg[0] = x;
+ 	extra->fcinfo.arg[1] = y;
+ 
+ 	/* just for paranoia's sake, we reset isnull each time */
+ 	extra->fcinfo.isnull = false;
+ 
+ 	result = FunctionCallInvoke(&extra->fcinfo);
+ 
+ 	/* Check for null result, since caller is clearly not expecting one */
+ 	if (extra->fcinfo.isnull)
+ 		elog(ERROR, "function %u returned NULL", extra->flinfo.fn_oid);
+ 
+ 	return result;
+ }
+ 
+ /*
+  * Set up a shim function to allow use of an old-style btree comparison
+  * function as if it were a sort support comparator.
+  */
+ void
+ PrepareSortSupportComparisonShim(Oid cmpFunc, SortSupport ssup)
+ {
+ 	SortShimExtra   *extra;
+ 
+ 	extra = (SortShimExtra *) MemoryContextAlloc(ssup->ssup_cxt,
+ 												 sizeof(SortShimExtra));
+ 
+ 	/* Lookup the comparison function */
+ 	fmgr_info_cxt(cmpFunc, &extra->flinfo, ssup->ssup_cxt);
+ 
+ 	/* We can initialize the callinfo just once and re-use it */
+ 	InitFunctionCallInfoData(extra->fcinfo, &extra->flinfo, 2,
+ 							 ssup->ssup_collation, NULL, NULL);
+ 	extra->fcinfo.argnull[0] = false;
+ 	extra->fcinfo.argnull[1] = false;
+ 
+ 	ssup->ssup_extra = extra;
+ 	ssup->comparator = comparison_shim;
+ }
+ 
+ /*
+  * Fill in SortSupport given an ordering operator (btree "<" or ">" operator).
+  *
+  * Caller must previously have zeroed the SortSupportData structure and then
+  * filled in ssup_cxt, ssup_collation, and ssup_nulls_first.  This will fill
+  * in ssup_reverse as well as the comparator function pointer.
+  */
+ void
+ PrepareSortSupportFromOrderingOp(Oid orderingOp, SortSupport ssup)
+ {
+ 	Oid			sortFunction;
+ 	bool		issupport;
+ 
+ 	if (!get_sort_function_for_ordering_op(orderingOp,
+ 										   &sortFunction,
+ 										   &issupport,
+ 										   &ssup->ssup_reverse))
+ 		elog(ERROR, "operator %u is not a valid ordering operator",
+ 			 orderingOp);
+ 
+ 	if (issupport)
+ 	{
+ 		/* The sort support function should provide a comparator */
+ 		OidFunctionCall1(sortFunction, PointerGetDatum(ssup));
+ 		Assert(ssup->comparator != NULL);
+ 	}
+ 	else
+ 	{
+ 		/* We'll use a shim to call the old-style btree comparator */
+ 		PrepareSortSupportComparisonShim(sortFunction, ssup);
+ 	}
+ }
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 3505236e5d3b9c753fed11822e7d4b147b0db0eb..28d585fc3b5f9a501253dd4b9d9042e0e33d1d0e 100644
*** a/src/backend/utils/sort/tuplesort.c
--- b/src/backend/utils/sort/tuplesort.c
***************
*** 112,117 ****
--- 112,118 ----
  #include "utils/memutils.h"
  #include "utils/pg_rusage.h"
  #include "utils/rel.h"
+ #include "utils/sortsupport.h"
  #include "utils/tuplesort.h"
  
  
*************** struct Tuplesortstate
*** 339,345 ****
  	 * tuplesort_begin_heap and used only by the MinimalTuple routines.
  	 */
  	TupleDesc	tupDesc;
! 	ScanKey		scanKeys;		/* array of length nKeys */
  
  	/*
  	 * These variables are specific to the CLUSTER case; they are set by
--- 340,346 ----
  	 * tuplesort_begin_heap and used only by the MinimalTuple routines.
  	 */
  	TupleDesc	tupDesc;
! 	SortSupport	sortKeys;		/* array of length nKeys */
  
  	/*
  	 * These variables are specific to the CLUSTER case; they are set by
*************** struct Tuplesortstate
*** 367,375 ****
  	 * tuplesort_begin_datum and used only by the DatumTuple routines.
  	 */
  	Oid			datumType;
! 	FmgrInfo	sortOpFn;		/* cached lookup data for sortOperator */
! 	int			sortFnFlags;	/* equivalent to sk_flags */
! 	Oid			sortCollation;	/* equivalent to sk_collation */
  	/* we need typelen and byval in order to know how to copy the Datums. */
  	int			datumTypeLen;
  	bool		datumTypeByVal;
--- 368,374 ----
  	 * tuplesort_begin_datum and used only by the DatumTuple routines.
  	 */
  	Oid			datumType;
! 	SortSupport	datumKey;
  	/* we need typelen and byval in order to know how to copy the Datums. */
  	int			datumTypeLen;
  	bool		datumTypeByVal;
*************** tuplesort_begin_heap(TupleDesc tupDesc,
*** 613,653 ****
  	state->reversedirection = reversedirection_heap;
  
  	state->tupDesc = tupDesc;	/* assume we need not copy tupDesc */
! 	state->scanKeys = (ScanKey) palloc0(nkeys * sizeof(ScanKeyData));
  
  	for (i = 0; i < nkeys; i++)
  	{
! 		Oid			sortFunction;
! 		bool		reverse;
! 		int			flags;
  
  		AssertArg(attNums[i] != 0);
  		AssertArg(sortOperators[i] != 0);
  
! 		if (!get_compare_function_for_ordering_op(sortOperators[i],
! 												  &sortFunction, &reverse))
! 			elog(ERROR, "operator %u is not a valid ordering operator",
! 				 sortOperators[i]);
! 
! 		/* We use btree's conventions for encoding directionality */
! 		flags = 0;
! 		if (reverse)
! 			flags |= SK_BT_DESC;
! 		if (nullsFirstFlags[i])
! 			flags |= SK_BT_NULLS_FIRST;
  
! 		/*
! 		 * We needn't fill in sk_strategy or sk_subtype since these scankeys
! 		 * will never be passed to an index.
! 		 */
! 		ScanKeyEntryInitialize(&state->scanKeys[i],
! 							   flags,
! 							   attNums[i],
! 							   InvalidStrategy,
! 							   InvalidOid,
! 							   sortCollations[i],
! 							   sortFunction,
! 							   (Datum) 0);
  	}
  
  	MemoryContextSwitchTo(oldcontext);
--- 612,634 ----
  	state->reversedirection = reversedirection_heap;
  
  	state->tupDesc = tupDesc;	/* assume we need not copy tupDesc */
! 
! 	/* Prepare SortSupport data for each column */
! 	state->sortKeys = (SortSupport) palloc0(nkeys * sizeof(SortSupportData));
  
  	for (i = 0; i < nkeys; i++)
  	{
! 		SortSupport	sortKey = state->sortKeys + i;
  
  		AssertArg(attNums[i] != 0);
  		AssertArg(sortOperators[i] != 0);
  
! 		sortKey->ssup_cxt = CurrentMemoryContext;
! 		sortKey->ssup_collation = sortCollations[i];
! 		sortKey->ssup_nulls_first = nullsFirstFlags[i];
! 		sortKey->ssup_attno = attNums[i];
  
! 		PrepareSortSupportFromOrderingOp(sortOperators[i], sortKey);
  	}
  
  	MemoryContextSwitchTo(oldcontext);
*************** tuplesort_begin_datum(Oid datumType, Oid
*** 799,806 ****
  {
  	Tuplesortstate *state = tuplesort_begin_common(workMem, randomAccess);
  	MemoryContext oldcontext;
- 	Oid			sortFunction;
- 	bool		reverse;
  	int16		typlen;
  	bool		typbyval;
  
--- 780,785 ----
*************** tuplesort_begin_datum(Oid datumType, Oid
*** 829,846 ****
  
  	state->datumType = datumType;
  
! 	/* lookup the ordering function */
! 	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);
  
! 	/* set ordering flags and collation */
! 	state->sortFnFlags = reverse ? SK_BT_DESC : 0;
! 	if (nullsFirstFlag)
! 		state->sortFnFlags |= SK_BT_NULLS_FIRST;
! 	state->sortCollation = sortCollation;
  
  	/* lookup necessary attributes of the datum type */
  	get_typlenbyval(datumType, &typlen, &typbyval);
--- 808,821 ----
  
  	state->datumType = datumType;
  
! 	/* Prepare SortSupport data */
! 	state->datumKey = (SortSupport) palloc0(sizeof(SortSupportData));
  
! 	state->datumKey->ssup_cxt = CurrentMemoryContext;
! 	state->datumKey->ssup_collation = sortCollation;
! 	state->datumKey->ssup_nulls_first = nullsFirstFlag;
! 
! 	PrepareSortSupportFromOrderingOp(sortOperator, state->datumKey);
  
  	/* lookup necessary attributes of the datum type */
  	get_typlenbyval(datumType, &typlen, &typbyval);
*************** markrunend(Tuplesortstate *state, int ta
*** 2605,2633 ****
  
  
  /*
-  * Set up for an external caller of ApplySortFunction.	This function
-  * basically just exists to localize knowledge of the encoding of sk_flags
-  * used in this module.
-  */
- void
- SelectSortFunction(Oid sortOperator,
- 				   bool nulls_first,
- 				   Oid *sortFunction,
- 				   int *sortFlags)
- {
- 	bool		reverse;
- 
- 	if (!get_compare_function_for_ordering_op(sortOperator,
- 											  sortFunction, &reverse))
- 		elog(ERROR, "operator %u is not a valid ordering operator",
- 			 sortOperator);
- 
- 	*sortFlags = reverse ? SK_BT_DESC : 0;
- 	if (nulls_first)
- 		*sortFlags |= SK_BT_NULLS_FIRST;
- }
- 
- /*
   * Inline-able copy of FunctionCall2Coll() to save some cycles in sorting.
   */
  static inline Datum
--- 2580,2585 ----
*************** inlineApplySortFunction(FmgrInfo *sortFu
*** 2693,2712 ****
  	return compare;
  }
  
- /*
-  * Non-inline ApplySortFunction() --- this is needed only to conform to
-  * C99's brain-dead notions about how to implement inline functions...
-  */
- int32
- ApplySortFunction(FmgrInfo *sortFunction, int sortFlags, Oid collation,
- 				  Datum datum1, bool isNull1,
- 				  Datum datum2, bool isNull2)
- {
- 	return inlineApplySortFunction(sortFunction, sortFlags, collation,
- 								   datum1, isNull1,
- 								   datum2, isNull2);
- }
- 
  
  /*
   * Routines specialized for HeapTuple (actually MinimalTuple) case
--- 2645,2650 ----
*************** ApplySortFunction(FmgrInfo *sortFunction
*** 2715,2721 ****
  static int
  comparetup_heap(const SortTuple *a, const SortTuple *b, Tuplesortstate *state)
  {
! 	ScanKey		scanKey = state->scanKeys;
  	HeapTupleData ltup;
  	HeapTupleData rtup;
  	TupleDesc	tupDesc;
--- 2653,2659 ----
  static int
  comparetup_heap(const SortTuple *a, const SortTuple *b, Tuplesortstate *state)
  {
! 	SortSupport	sortKey = state->sortKeys;
  	HeapTupleData ltup;
  	HeapTupleData rtup;
  	TupleDesc	tupDesc;
*************** comparetup_heap(const SortTuple *a, cons
*** 2726,2735 ****
  	CHECK_FOR_INTERRUPTS();
  
  	/* Compare the leading sort key */
! 	compare = inlineApplySortFunction(&scanKey->sk_func, scanKey->sk_flags,
! 									  scanKey->sk_collation,
! 									  a->datum1, a->isnull1,
! 									  b->datum1, b->isnull1);
  	if (compare != 0)
  		return compare;
  
--- 2664,2672 ----
  	CHECK_FOR_INTERRUPTS();
  
  	/* Compare the leading sort key */
! 	compare = ApplySortComparator(a->datum1, a->isnull1,
! 								  b->datum1, b->isnull1,
! 								  sortKey);
  	if (compare != 0)
  		return compare;
  
*************** comparetup_heap(const SortTuple *a, cons
*** 2739,2748 ****
  	rtup.t_len = ((MinimalTuple) b->tuple)->t_len + MINIMAL_TUPLE_OFFSET;
  	rtup.t_data = (HeapTupleHeader) ((char *) b->tuple - MINIMAL_TUPLE_OFFSET);
  	tupDesc = state->tupDesc;
! 	scanKey++;
! 	for (nkey = 1; nkey < state->nKeys; nkey++, scanKey++)
  	{
! 		AttrNumber	attno = scanKey->sk_attno;
  		Datum		datum1,
  					datum2;
  		bool		isnull1,
--- 2676,2685 ----
  	rtup.t_len = ((MinimalTuple) b->tuple)->t_len + MINIMAL_TUPLE_OFFSET;
  	rtup.t_data = (HeapTupleHeader) ((char *) b->tuple - MINIMAL_TUPLE_OFFSET);
  	tupDesc = state->tupDesc;
! 	sortKey++;
! 	for (nkey = 1; nkey < state->nKeys; nkey++, sortKey++)
  	{
! 		AttrNumber	attno = sortKey->ssup_attno;
  		Datum		datum1,
  					datum2;
  		bool		isnull1,
*************** comparetup_heap(const SortTuple *a, cons
*** 2751,2760 ****
  		datum1 = heap_getattr(&ltup, attno, tupDesc, &isnull1);
  		datum2 = heap_getattr(&rtup, attno, tupDesc, &isnull2);
  
! 		compare = inlineApplySortFunction(&scanKey->sk_func, scanKey->sk_flags,
! 										  scanKey->sk_collation,
! 										  datum1, isnull1,
! 										  datum2, isnull2);
  		if (compare != 0)
  			return compare;
  	}
--- 2688,2696 ----
  		datum1 = heap_getattr(&ltup, attno, tupDesc, &isnull1);
  		datum2 = heap_getattr(&rtup, attno, tupDesc, &isnull2);
  
! 		compare = ApplySortComparator(datum1, isnull1,
! 									  datum2, isnull2,
! 									  sortKey);
  		if (compare != 0)
  			return compare;
  	}
*************** copytup_heap(Tuplesortstate *state, Sort
*** 2781,2787 ****
  	htup.t_len = tuple->t_len + MINIMAL_TUPLE_OFFSET;
  	htup.t_data = (HeapTupleHeader) ((char *) tuple - MINIMAL_TUPLE_OFFSET);
  	stup->datum1 = heap_getattr(&htup,
! 								state->scanKeys[0].sk_attno,
  								state->tupDesc,
  								&stup->isnull1);
  }
--- 2717,2723 ----
  	htup.t_len = tuple->t_len + MINIMAL_TUPLE_OFFSET;
  	htup.t_data = (HeapTupleHeader) ((char *) tuple - MINIMAL_TUPLE_OFFSET);
  	stup->datum1 = heap_getattr(&htup,
! 								state->sortKeys[0].ssup_attno,
  								state->tupDesc,
  								&stup->isnull1);
  }
*************** readtup_heap(Tuplesortstate *state, Sort
*** 2833,2839 ****
  	htup.t_len = tuple->t_len + MINIMAL_TUPLE_OFFSET;
  	htup.t_data = (HeapTupleHeader) ((char *) tuple - MINIMAL_TUPLE_OFFSET);
  	stup->datum1 = heap_getattr(&htup,
! 								state->scanKeys[0].sk_attno,
  								state->tupDesc,
  								&stup->isnull1);
  }
--- 2769,2775 ----
  	htup.t_len = tuple->t_len + MINIMAL_TUPLE_OFFSET;
  	htup.t_data = (HeapTupleHeader) ((char *) tuple - MINIMAL_TUPLE_OFFSET);
  	stup->datum1 = heap_getattr(&htup,
! 								state->sortKeys[0].ssup_attno,
  								state->tupDesc,
  								&stup->isnull1);
  }
*************** readtup_heap(Tuplesortstate *state, Sort
*** 2841,2852 ****
  static void
  reversedirection_heap(Tuplesortstate *state)
  {
! 	ScanKey		scanKey = state->scanKeys;
  	int			nkey;
  
! 	for (nkey = 0; nkey < state->nKeys; nkey++, scanKey++)
  	{
! 		scanKey->sk_flags ^= (SK_BT_DESC | SK_BT_NULLS_FIRST);
  	}
  }
  
--- 2777,2789 ----
  static void
  reversedirection_heap(Tuplesortstate *state)
  {
! 	SortSupport	sortKey = state->sortKeys;
  	int			nkey;
  
! 	for (nkey = 0; nkey < state->nKeys; nkey++, sortKey++)
  	{
! 		sortKey->ssup_reverse = !sortKey->ssup_reverse;
! 		sortKey->ssup_nulls_first = !sortKey->ssup_nulls_first;
  	}
  }
  
*************** comparetup_datum(const SortTuple *a, con
*** 3297,3306 ****
  	/* Allow interrupting long sorts */
  	CHECK_FOR_INTERRUPTS();
  
! 	return inlineApplySortFunction(&state->sortOpFn, state->sortFnFlags,
! 								   state->sortCollation,
! 								   a->datum1, a->isnull1,
! 								   b->datum1, b->isnull1);
  }
  
  static void
--- 3234,3242 ----
  	/* Allow interrupting long sorts */
  	CHECK_FOR_INTERRUPTS();
  
! 	return ApplySortComparator(a->datum1, a->isnull1,
! 							   b->datum1, b->isnull1,
! 							   state->datumKey);
  }
  
  static void
*************** readtup_datum(Tuplesortstate *state, Sor
*** 3392,3398 ****
  static void
  reversedirection_datum(Tuplesortstate *state)
  {
! 	state->sortFnFlags ^= (SK_BT_DESC | SK_BT_NULLS_FIRST);
  }
  
  /*
--- 3328,3335 ----
  static void
  reversedirection_datum(Tuplesortstate *state)
  {
! 	state->datumKey->ssup_reverse = !state->datumKey->ssup_reverse;
! 	state->datumKey->ssup_nulls_first = !state->datumKey->ssup_nulls_first;
  }
  
  /*
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 347d9423ba3d092235033d2447fe12f21f43011d..9a751ce100455f81740fa29491d03b050572bee9 100644
*** a/src/include/access/nbtree.h
--- b/src/include/access/nbtree.h
*************** typedef struct xl_btree_newroot
*** 417,429 ****
  
  /*
   *	When a new operator class is declared, we require that the user
!  *	supply us with an amproc procedure for determining whether, for
!  *	two keys a and b, a < b, a = b, or a > b.  This routine must
!  *	return < 0, 0, > 0, respectively, in these three cases.  Since we
!  *	only have one such proc in amproc, it's number 1.
   */
  
! #define BTORDER_PROC	1
  
  /*
   *	We need to be able to tell the difference between read and write
--- 417,434 ----
  
  /*
   *	When a new operator class is declared, we require that the user
!  *	supply us with an amproc procedure (BTORDER_PROC) for determining
!  *	whether, for two keys a and b, a < b, a = b, or a > b.  This routine
!  *	must return < 0, 0, > 0, respectively, in these three cases.  (It must
!  *	not return INT_MIN, since we may negate the result before using it.)
!  *
!  *	To facilitate accelerated sorting, an operator class may choose to
!  *	offer a second procedure (BTSORTSUPPORT_PROC).  For full details, see
!  *	src/include/utils/sortsupport.h.
   */
  
! #define BTORDER_PROC		1
! #define BTSORTSUPPORT_PROC	2
  
  /*
   *	We need to be able to tell the difference between read and write
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index 8b075d30d689a9f7fa022d3f554626b6f5086883..ddacdf274c49ac7e83b941aa983dbf96214f5812 100644
*** a/src/include/catalog/pg_am.h
--- b/src/include/catalog/pg_am.h
*************** typedef FormData_pg_am *Form_pg_am;
*** 117,123 ****
   * ----------------
   */
  
! DATA(insert OID = 403 (  btree	5 1 t f t t t t t t t f t t 0 btinsert btbeginscan btgettuple btgetbitmap btrescan btendscan btmarkpos btrestrpos btbuild btbuildempty btbulkdelete btvacuumcleanup btcostestimate btoptions ));
  DESCR("b-tree index access method");
  #define BTREE_AM_OID 403
  DATA(insert OID = 405 (  hash	1 1 f f t f f f f f f f f f 23 hashinsert hashbeginscan hashgettuple hashgetbitmap hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbuildempty hashbulkdelete hashvacuumcleanup hashcostestimate hashoptions ));
--- 117,123 ----
   * ----------------
   */
  
! DATA(insert OID = 403 (  btree	5 2 t f t t t t t t t f t t 0 btinsert btbeginscan btgettuple btgetbitmap btrescan btendscan btmarkpos btrestrpos btbuild btbuildempty btbulkdelete btvacuumcleanup btcostestimate btoptions ));
  DESCR("b-tree index access method");
  #define BTREE_AM_OID 403
  DATA(insert OID = 405 (  hash	1 1 f f t f f f f f f f f f 23 hashinsert hashbeginscan hashgettuple hashgetbitmap hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbuildempty hashbulkdelete hashvacuumcleanup hashcostestimate hashoptions ));
diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h
index e5d43f7c1d3ef02322ad2c5b376885a17e5f7430..9d0cb28fac8b772ea384ba72aa6ac70fa4174644 100644
*** a/src/include/catalog/pg_amproc.h
--- b/src/include/catalog/pg_amproc.h
*************** DATA(insert (	1976   21 21 1 350 ));
*** 100,105 ****
--- 100,106 ----
  DATA(insert (	1976   21 23 1 2190 ));
  DATA(insert (	1976   21 20 1 2192 ));
  DATA(insert (	1976   23 23 1 351 ));
+ DATA(insert (	1976   23 23 2 321 ));
  DATA(insert (	1976   23 20 1 2188 ));
  DATA(insert (	1976   23 21 1 2191 ));
  DATA(insert (	1976   20 20 1 842 ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 28e53b7b7c367731b5b0102f5afe032b9bbf09a0..3bd52e7add9645875c31f518baaff28824bf9f85 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 350 (  btint2cmp		   P
*** 568,573 ****
--- 568,575 ----
  DESCR("less-equal-greater");
  DATA(insert OID = 351 (  btint4cmp		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ btint4cmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
+ DATA(insert OID = 321 (  btint4sortsupport PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2278 "2281" _null_ _null_ _null_ _null_ btint4sortsupport _null_ _null_ _null_ ));
+ DESCR("sort support");
  DATA(insert OID = 842 (  btint8cmp		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 23 "20 20" _null_ _null_ _null_ _null_ btint8cmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 354 (  btfloat4cmp	   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 23 "700 700" _null_ _null_ _null_ _null_ btfloat4cmp _null_ _null_ _null_ ));
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0a89f189d7c6d30ff7f3065cdd8ea360f402264d..f5935e2106f67562caa8f589f77c7cfc2a1d14fe 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 20,25 ****
--- 20,26 ----
  #include "nodes/params.h"
  #include "nodes/plannodes.h"
  #include "utils/reltrigger.h"
+ #include "utils/sortsupport.h"
  #include "utils/tuplestore.h"
  
  
*************** typedef struct AppendState
*** 1087,1093 ****
   *
   *		nplans			how many plans are in the array
   *		nkeys			number of sort key columns
!  *		scankeys		sort keys in ScanKey representation
   *		slots			current output tuple of each subplan
   *		heap			heap of active tuples (represented as array indexes)
   *		heap_size		number of active heap entries
--- 1088,1094 ----
   *
   *		nplans			how many plans are in the array
   *		nkeys			number of sort key columns
!  *		sortkeys		sort keys in SortSupport representation
   *		slots			current output tuple of each subplan
   *		heap			heap of active tuples (represented as array indexes)
   *		heap_size		number of active heap entries
*************** typedef struct MergeAppendState
*** 1101,1107 ****
  	PlanState **mergeplans;		/* array of PlanStates for my inputs */
  	int			ms_nplans;
  	int			ms_nkeys;
! 	ScanKey		ms_scankeys;	/* array of length ms_nkeys */
  	TupleTableSlot **ms_slots;	/* array of length ms_nplans */
  	int		   *ms_heap;		/* array of length ms_nplans */
  	int			ms_heap_size;	/* current active length of ms_heap[] */
--- 1102,1108 ----
  	PlanState **mergeplans;		/* array of PlanStates for my inputs */
  	int			ms_nplans;
  	int			ms_nkeys;
! 	SortSupport	ms_sortkeys;	/* array of length ms_nkeys */
  	TupleTableSlot **ms_slots;	/* array of length ms_nplans */
  	int		   *ms_heap;		/* array of length ms_nplans */
  	int			ms_heap_size;	/* current active length of ms_heap[] */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 47a14125c48fc384b5d5c8d77abad99cec114e22..9a43e490b1d2d91c8d19a9c68524b54a3831a727 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern void pg_lltoa(int64 ll, char *a);
*** 279,285 ****
  
  /*
   *		Per-opclass comparison functions for new btrees.  These are
!  *		stored in pg_amproc and defined in access/nbtree/nbtcompare.c
   */
  extern Datum btboolcmp(PG_FUNCTION_ARGS);
  extern Datum btint2cmp(PG_FUNCTION_ARGS);
--- 279,285 ----
  
  /*
   *		Per-opclass comparison functions for new btrees.  These are
!  *		stored in pg_amproc; most are defined in access/nbtree/nbtcompare.c
   */
  extern Datum btboolcmp(PG_FUNCTION_ARGS);
  extern Datum btint2cmp(PG_FUNCTION_ARGS);
*************** extern Datum btcharcmp(PG_FUNCTION_ARGS)
*** 304,309 ****
--- 304,316 ----
  extern Datum btnamecmp(PG_FUNCTION_ARGS);
  extern Datum bttextcmp(PG_FUNCTION_ARGS);
  
+ /*
+  *		Per-opclass sort support functions for new btrees.  Like the
+  *		functions above, these are stored in pg_amproc; most are defined in
+  *		access/nbtree/nbtcompare.c
+  */
+ extern Datum btint4sortsupport(PG_FUNCTION_ARGS);
+ 
  /* float.c */
  extern PGDLLIMPORT int extra_float_digits;
  
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index d3ad4f14282641fc7ba35f3188d76a8b097dd22e..311aa25862eba1e83af367dd7f4b81860538feb7 100644
*** a/src/include/utils/lsyscache.h
--- b/src/include/utils/lsyscache.h
*************** extern Oid get_opfamily_member(Oid opfam
*** 48,55 ****
  					int16 strategy);
  extern bool get_ordering_op_properties(Oid opno,
  						   Oid *opfamily, Oid *opcintype, int16 *strategy);
! extern bool get_compare_function_for_ordering_op(Oid opno,
! 									 Oid *cmpfunc, bool *reverse);
  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);
--- 48,55 ----
  					int16 strategy);
  extern bool get_ordering_op_properties(Oid opno,
  						   Oid *opfamily, Oid *opcintype, int16 *strategy);
! extern bool get_sort_function_for_ordering_op(Oid opno, Oid *sortfunc,
! 								  bool *issupport, bool *reverse);
  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);
diff --git a/src/include/utils/sortsupport.h b/src/include/utils/sortsupport.h
index ...bd6dde34729aa03ca7b80f66de24744136732a86 .
*** a/src/include/utils/sortsupport.h
--- b/src/include/utils/sortsupport.h
***************
*** 0 ****
--- 1,156 ----
+ /*-------------------------------------------------------------------------
+  *
+  * sortsupport.h
+  *	  Framework for accelerated sorting.
+  *
+  * Traditionally, PostgreSQL has implemented sorting by repeatedly invoking
+  * an SQL-callable comparison function "cmp(x, y) returns int" on pairs of
+  * values to be compared, where the comparison function is the BTORDER_PROC
+  * pg_amproc support function of the appropriate btree index opclass.
+  *
+  * This file defines alternative APIs that allow sorting to be performed with
+  * reduced overhead.  To support lower-overhead sorting, a btree opclass may
+  * provide a BTSORTSUPPORT_PROC pg_amproc entry, which must take a single
+  * argument of type internal and return void.  The argument is actually a
+  * pointer to a SortSupportData struct, which is defined below.
+  *
+  * If provided, the BTSORTSUPPORT function will be called during sort setup,
+  * and it must initialize the provided struct with pointers to function(s)
+  * that can be called to perform sorting.  This API is defined to allow
+  * multiple acceleration mechanisms to be supported, but no opclass is
+  * required to provide all of them.  The BTSORTSUPPORT function should
+  * simply not set any function pointers for mechanisms it doesn't support.
+  * (However, all opclasses that provide BTSORTSUPPORT are required to provide
+  * the comparator function.)
+  *
+  * All sort support functions will be passed the address of the
+  * SortSupportData struct when called, so they can use it to store
+  * additional private data as needed.  In particular, for collation-aware
+  * datatypes, the ssup_collation field is set before calling BTSORTSUPPORT
+  * and is available to all support functions.  Additional opclass-dependent
+  * data can be stored using the ssup_extra field.  Any such data
+  * should be allocated in the ssup_cxt memory context.
+  *
+  * Note: since pg_amproc functions are indexed by (lefttype, righttype)
+  * it is possible to associate a BTSORTSUPPORT function with a cross-type
+  * comparison.  This could sensibly be used to provide a fast comparator
+  * function for such cases, but probably not any other acceleration method.
+  *
+  *
+  * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * src/include/utils/sortsupport.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef SORTSUPPORT_H
+ #define SORTSUPPORT_H
+ 
+ #include "access/attnum.h"
+ 
+ typedef struct SortSupportData *SortSupport;
+ 
+ typedef struct SortSupportData
+ {
+ 	/*
+ 	 * These fields are initialized before calling the BTSORTSUPPORT function
+ 	 * and should not be changed later.
+ 	 */
+ 	MemoryContext ssup_cxt;				/* Context containing sort info */
+ 	Oid			ssup_collation;			/* Collation to use, or InvalidOid */
+ 
+ 	/*
+ 	 * Additional sorting parameters; but unlike ssup_collation, these can
+ 	 * be changed after BTSORTSUPPORT is called, so don't use them in
+ 	 * selecting sort support functions.
+ 	 */
+ 	bool		ssup_reverse;			/* descending-order sort? */
+ 	bool		ssup_nulls_first;		/* sort nulls first? */
+ 
+ 	/*
+ 	 * These fields are workspace for callers, and should not be touched by
+ 	 * opclass-specific functions.
+ 	 */
+ 	AttrNumber	ssup_attno;				/* column number to sort */
+ 
+ 	/*
+ 	 * ssup_extra is zeroed before calling the BTSORTSUPPORT function, and
+ 	 * is not touched subsequently by callers.
+ 	 */
+ 	void	   *ssup_extra;				/* Workspace for opclass functions */
+ 
+ 	/*
+ 	 * Function pointers are zeroed before calling the BTSORTSUPPORT function,
+ 	 * and must be set by it for any acceleration methods it wants to supply.
+ 	 * The comparator pointer must be set, others are optional.
+ 	 */
+ 
+ 	/*
+ 	 * Comparator function has the same API as the traditional btree
+ 	 * comparison function, ie, return <0, 0, or >0 according as x is less
+ 	 * than, equal to, or greater than y.  Note that x and y are guaranteed
+ 	 * not null, and there is no way to return null either.  Do not return
+ 	 * INT_MIN, as callers are allowed to negate the result before using it.
+ 	 */
+ 	int			(*comparator) (Datum x, Datum y, SortSupport ssup);
+ 
+ 	/*
+ 	 * Additional sort-acceleration functions might be added here later.
+ 	 */
+ } SortSupportData;
+ 
+ 
+ /* ApplySortComparator should be inlined if possible */
+ #ifdef USE_INLINE
+ 
+ /*
+  * Apply a sort comparator function and return a 3-way comparison result.
+  * This takes care of handling reverse-sort and NULLs-ordering properly.
+  */
+ static inline int
+ ApplySortComparator(Datum datum1, bool isNull1,
+ 					Datum datum2, bool isNull2,
+ 					SortSupport ssup)
+ {
+ 	int			compare;
+ 
+ 	if (isNull1)
+ 	{
+ 		if (isNull2)
+ 			compare = 0;		/* NULL "=" NULL */
+ 		else if (ssup->ssup_nulls_first)
+ 			compare = -1;		/* NULL "<" NOT_NULL */
+ 		else
+ 			compare = 1;		/* NULL ">" NOT_NULL */
+ 	}
+ 	else if (isNull2)
+ 	{
+ 		if (ssup->ssup_nulls_first)
+ 			compare = 1;		/* NOT_NULL ">" NULL */
+ 		else
+ 			compare = -1;		/* NOT_NULL "<" NULL */
+ 	}
+ 	else
+ 	{
+ 		compare = (*ssup->comparator) (datum1, datum2, ssup);
+ 		if (ssup->ssup_reverse)
+ 			compare = -compare;
+ 	}
+ 
+ 	return compare;
+ }
+ 
+ #else
+ 
+ extern int	ApplySortComparator(Datum datum1, bool isNull1,
+ 					Datum datum2, bool isNull2,
+ 					SortSupport ssup);
+ 
+ #endif   /* USE_INLINE */
+ 
+ /* Other functions in utils/sort/sortsupport.c */
+ extern void PrepareSortSupportComparisonShim(Oid cmpFunc, SortSupport ssup);
+ extern void PrepareSortSupportFromOrderingOp(Oid orderingOp, SortSupport ssup);
+ 
+ #endif   /* SORTSUPPORT_H */
diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h
index 1ebcbfe17242a8e5262fe5d942210d0d77b1dd73..5c48e0e10f593de6ef51b42c75f150eae2be9cbc 100644
*** a/src/include/utils/tuplesort.h
--- b/src/include/utils/tuplesort.h
*************** extern void tuplesort_rescan(Tuplesortst
*** 116,134 ****
  extern void tuplesort_markpos(Tuplesortstate *state);
  extern void tuplesort_restorepos(Tuplesortstate *state);
  
- /* Setup for ApplySortFunction */
- extern void SelectSortFunction(Oid sortOperator, bool nulls_first,
- 				   Oid *sortFunction,
- 				   int *sortFlags);
- 
- /*
-  * Apply a sort function (by now converted to fmgr lookup form)
-  * and return a 3-way comparison result.  This takes care of handling
-  * reverse-sort and NULLs-ordering properly.
-  */
- extern int32 ApplySortFunction(FmgrInfo *sortFunction, int sortFlags,
- 				  Oid collation,
- 				  Datum datum1, bool isNull1,
- 				  Datum datum2, bool isNull2);
- 
  #endif   /* TUPLESORT_H */
--- 116,119 ----
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index b915134fff33d3154a24d56361d0427810b90af1..a0ffd77e0ed889001b11fe174d62737e0d281d5e 100644
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
*************** WHERE p1.amprocfamily = p3.oid AND p3.op
*** 1186,1225 ****
  
  -- Detect missing pg_amproc entries: should have as many support functions
  -- as AM expects for each datatype combination supported by the opfamily.
! -- GiST/GIN are special cases because each has an optional support function.
! SELECT p1.amname, p2.opfname, p3.amproclefttype, p3.amprocrighttype
! FROM pg_am AS p1, pg_opfamily AS p2, pg_amproc AS p3
! WHERE p2.opfmethod = p1.oid AND p3.amprocfamily = p2.oid AND
!     p1.amname <> 'gist' AND p1.amname <> 'gin' AND
!     p1.amsupport != (SELECT count(*) FROM pg_amproc AS p4
!                      WHERE p4.amprocfamily = p2.oid AND
!                            p4.amproclefttype = p3.amproclefttype AND
!                            p4.amprocrighttype = p3.amprocrighttype);
!  amname | opfname | amproclefttype | amprocrighttype 
! --------+---------+----------------+-----------------
! (0 rows)
! 
! -- Similar check for GiST/GIN, allowing one optional proc
  SELECT p1.amname, p2.opfname, p3.amproclefttype, p3.amprocrighttype
  FROM pg_am AS p1, pg_opfamily AS p2, pg_amproc AS p3
  WHERE p2.opfmethod = p1.oid AND p3.amprocfamily = p2.oid AND
-     (p1.amname = 'gist' OR p1.amname = 'gin') AND
      (SELECT count(*) FROM pg_amproc AS p4
       WHERE p4.amprocfamily = p2.oid AND
             p4.amproclefttype = p3.amproclefttype AND
             p4.amprocrighttype = p3.amprocrighttype)
!       NOT IN (p1.amsupport, p1.amsupport - 1);
   amname | opfname | amproclefttype | amprocrighttype 
  --------+---------+----------------+-----------------
  (0 rows)
  
  -- Also, check if there are any pg_opclass entries that don't seem to have
! -- pg_amproc support.  Again, GiST/GIN have to be checked specially.
  SELECT amname, opcname, count(*)
  FROM pg_am am JOIN pg_opclass op ON opcmethod = am.oid
       LEFT JOIN pg_amproc p ON amprocfamily = opcfamily AND
           amproclefttype = amprocrighttype AND amproclefttype = opcintype
! WHERE am.amname <> 'gist' AND am.amname <> 'gin'
  GROUP BY amname, amsupport, opcname, amprocfamily
  HAVING count(*) != amsupport OR amprocfamily IS NULL;
   amname | opcname | count 
--- 1186,1215 ----
  
  -- Detect missing pg_amproc entries: should have as many support functions
  -- as AM expects for each datatype combination supported by the opfamily.
! -- btree/GiST/GIN each allow one optional support function, though.
  SELECT p1.amname, p2.opfname, p3.amproclefttype, p3.amprocrighttype
  FROM pg_am AS p1, pg_opfamily AS p2, pg_amproc AS p3
  WHERE p2.opfmethod = p1.oid AND p3.amprocfamily = p2.oid AND
      (SELECT count(*) FROM pg_amproc AS p4
       WHERE p4.amprocfamily = p2.oid AND
             p4.amproclefttype = p3.amproclefttype AND
             p4.amprocrighttype = p3.amprocrighttype)
!     NOT BETWEEN
!       (CASE WHEN p1.amname IN ('btree', 'gist', 'gin') THEN p1.amsupport - 1
!             ELSE p1.amsupport END)
!       AND p1.amsupport;
   amname | opfname | amproclefttype | amprocrighttype 
  --------+---------+----------------+-----------------
  (0 rows)
  
  -- Also, check if there are any pg_opclass entries that don't seem to have
! -- pg_amproc support.  Again, opclasses with an optional support proc have
! -- to be checked specially.
  SELECT amname, opcname, count(*)
  FROM pg_am am JOIN pg_opclass op ON opcmethod = am.oid
       LEFT JOIN pg_amproc p ON amprocfamily = opcfamily AND
           amproclefttype = amprocrighttype AND amproclefttype = opcintype
! WHERE am.amname <> 'btree' AND am.amname <> 'gist' AND am.amname <> 'gin'
  GROUP BY amname, amsupport, opcname, amprocfamily
  HAVING count(*) != amsupport OR amprocfamily IS NULL;
   amname | opcname | count 
*************** SELECT amname, opcname, count(*)
*** 1230,1236 ****
  FROM pg_am am JOIN pg_opclass op ON opcmethod = am.oid
       LEFT JOIN pg_amproc p ON amprocfamily = opcfamily AND
           amproclefttype = amprocrighttype AND amproclefttype = opcintype
! WHERE am.amname = 'gist' OR am.amname = 'gin'
  GROUP BY amname, amsupport, opcname, amprocfamily
  HAVING (count(*) != amsupport AND count(*) != amsupport - 1)
      OR amprocfamily IS NULL;
--- 1220,1226 ----
  FROM pg_am am JOIN pg_opclass op ON opcmethod = am.oid
       LEFT JOIN pg_amproc p ON amprocfamily = opcfamily AND
           amproclefttype = amprocrighttype AND amproclefttype = opcintype
! WHERE am.amname = 'btree' OR am.amname = 'gist' OR am.amname = 'gin'
  GROUP BY amname, amsupport, opcname, amprocfamily
  HAVING (count(*) != amsupport AND count(*) != amsupport - 1)
      OR amprocfamily IS NULL;
*************** WHERE p1.amprocfamily = p3.oid AND p4.am
*** 1261,1279 ****
  (0 rows)
  
  -- For btree, though, we can do better since we know the support routines
! -- must be of the form cmp(lefttype, righttype) returns int4.
  SELECT p1.amprocfamily, p1.amprocnum,
  	p2.oid, p2.proname,
  	p3.opfname
  FROM pg_amproc AS p1, pg_proc AS p2, pg_opfamily AS p3
  WHERE p3.opfmethod = (SELECT oid FROM pg_am WHERE amname = 'btree')
      AND p1.amprocfamily = p3.oid AND p1.amproc = p2.oid AND
!     (amprocnum != 1
!      OR proretset
!      OR prorettype != 'int4'::regtype
!      OR pronargs != 2
!      OR proargtypes[0] != amproclefttype
!      OR proargtypes[1] != amprocrighttype);
   amprocfamily | amprocnum | oid | proname | opfname 
  --------------+-----------+-----+---------+---------
  (0 rows)
--- 1251,1272 ----
  (0 rows)
  
  -- For btree, though, we can do better since we know the support routines
! -- must be of the form cmp(lefttype, righttype) returns int4
! -- or sortsupport(internal) returns void.
  SELECT p1.amprocfamily, p1.amprocnum,
  	p2.oid, p2.proname,
  	p3.opfname
  FROM pg_amproc AS p1, pg_proc AS p2, pg_opfamily AS p3
  WHERE p3.opfmethod = (SELECT oid FROM pg_am WHERE amname = 'btree')
      AND p1.amprocfamily = p3.oid AND p1.amproc = p2.oid AND
!     (CASE WHEN amprocnum = 1
!           THEN prorettype != 'int4'::regtype OR proretset OR pronargs != 2
!                OR proargtypes[0] != amproclefttype
!                OR proargtypes[1] != amprocrighttype
!           WHEN amprocnum = 2
!           THEN prorettype != 'void'::regtype OR proretset OR pronargs != 1
!                OR proargtypes[0] != 'internal'::regtype
!           ELSE true END);
   amprocfamily | amprocnum | oid | proname | opfname 
  --------------+-----------+-----+---------+---------
  (0 rows)
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index b0d143087e8fd68b30461c5d59b5bc5fdd3da086..6a79ea180c1c6c2d83891ca32a05e2249202ea5c 100644
*** a/src/test/regress/sql/opr_sanity.sql
--- b/src/test/regress/sql/opr_sanity.sql
*************** WHERE p1.amprocfamily = p3.oid AND p3.op
*** 926,962 ****
  
  -- Detect missing pg_amproc entries: should have as many support functions
  -- as AM expects for each datatype combination supported by the opfamily.
! -- GiST/GIN are special cases because each has an optional support function.
! 
! SELECT p1.amname, p2.opfname, p3.amproclefttype, p3.amprocrighttype
! FROM pg_am AS p1, pg_opfamily AS p2, pg_amproc AS p3
! WHERE p2.opfmethod = p1.oid AND p3.amprocfamily = p2.oid AND
!     p1.amname <> 'gist' AND p1.amname <> 'gin' AND
!     p1.amsupport != (SELECT count(*) FROM pg_amproc AS p4
!                      WHERE p4.amprocfamily = p2.oid AND
!                            p4.amproclefttype = p3.amproclefttype AND
!                            p4.amprocrighttype = p3.amprocrighttype);
! 
! -- Similar check for GiST/GIN, allowing one optional proc
  
  SELECT p1.amname, p2.opfname, p3.amproclefttype, p3.amprocrighttype
  FROM pg_am AS p1, pg_opfamily AS p2, pg_amproc AS p3
  WHERE p2.opfmethod = p1.oid AND p3.amprocfamily = p2.oid AND
-     (p1.amname = 'gist' OR p1.amname = 'gin') AND
      (SELECT count(*) FROM pg_amproc AS p4
       WHERE p4.amprocfamily = p2.oid AND
             p4.amproclefttype = p3.amproclefttype AND
             p4.amprocrighttype = p3.amprocrighttype)
!       NOT IN (p1.amsupport, p1.amsupport - 1);
  
  -- Also, check if there are any pg_opclass entries that don't seem to have
! -- pg_amproc support.  Again, GiST/GIN have to be checked specially.
  
  SELECT amname, opcname, count(*)
  FROM pg_am am JOIN pg_opclass op ON opcmethod = am.oid
       LEFT JOIN pg_amproc p ON amprocfamily = opcfamily AND
           amproclefttype = amprocrighttype AND amproclefttype = opcintype
! WHERE am.amname <> 'gist' AND am.amname <> 'gin'
  GROUP BY amname, amsupport, opcname, amprocfamily
  HAVING count(*) != amsupport OR amprocfamily IS NULL;
  
--- 926,954 ----
  
  -- Detect missing pg_amproc entries: should have as many support functions
  -- as AM expects for each datatype combination supported by the opfamily.
! -- btree/GiST/GIN each allow one optional support function, though.
  
  SELECT p1.amname, p2.opfname, p3.amproclefttype, p3.amprocrighttype
  FROM pg_am AS p1, pg_opfamily AS p2, pg_amproc AS p3
  WHERE p2.opfmethod = p1.oid AND p3.amprocfamily = p2.oid AND
      (SELECT count(*) FROM pg_amproc AS p4
       WHERE p4.amprocfamily = p2.oid AND
             p4.amproclefttype = p3.amproclefttype AND
             p4.amprocrighttype = p3.amprocrighttype)
!     NOT BETWEEN
!       (CASE WHEN p1.amname IN ('btree', 'gist', 'gin') THEN p1.amsupport - 1
!             ELSE p1.amsupport END)
!       AND p1.amsupport;
  
  -- Also, check if there are any pg_opclass entries that don't seem to have
! -- pg_amproc support.  Again, opclasses with an optional support proc have
! -- to be checked specially.
  
  SELECT amname, opcname, count(*)
  FROM pg_am am JOIN pg_opclass op ON opcmethod = am.oid
       LEFT JOIN pg_amproc p ON amprocfamily = opcfamily AND
           amproclefttype = amprocrighttype AND amproclefttype = opcintype
! WHERE am.amname <> 'btree' AND am.amname <> 'gist' AND am.amname <> 'gin'
  GROUP BY amname, amsupport, opcname, amprocfamily
  HAVING count(*) != amsupport OR amprocfamily IS NULL;
  
*************** SELECT amname, opcname, count(*)
*** 964,970 ****
  FROM pg_am am JOIN pg_opclass op ON opcmethod = am.oid
       LEFT JOIN pg_amproc p ON amprocfamily = opcfamily AND
           amproclefttype = amprocrighttype AND amproclefttype = opcintype
! WHERE am.amname = 'gist' OR am.amname = 'gin'
  GROUP BY amname, amsupport, opcname, amprocfamily
  HAVING (count(*) != amsupport AND count(*) != amsupport - 1)
      OR amprocfamily IS NULL;
--- 956,962 ----
  FROM pg_am am JOIN pg_opclass op ON opcmethod = am.oid
       LEFT JOIN pg_amproc p ON amprocfamily = opcfamily AND
           amproclefttype = amprocrighttype AND amproclefttype = opcintype
! WHERE am.amname = 'btree' OR am.amname = 'gist' OR am.amname = 'gin'
  GROUP BY amname, amsupport, opcname, amprocfamily
  HAVING (count(*) != amsupport AND count(*) != amsupport - 1)
      OR amprocfamily IS NULL;
*************** WHERE p1.amprocfamily = p3.oid AND p4.am
*** 990,996 ****
      (p2.proretset OR p5.proretset OR p2.pronargs != p5.pronargs);
  
  -- For btree, though, we can do better since we know the support routines
! -- must be of the form cmp(lefttype, righttype) returns int4.
  
  SELECT p1.amprocfamily, p1.amprocnum,
  	p2.oid, p2.proname,
--- 982,989 ----
      (p2.proretset OR p5.proretset OR p2.pronargs != p5.pronargs);
  
  -- For btree, though, we can do better since we know the support routines
! -- must be of the form cmp(lefttype, righttype) returns int4
! -- or sortsupport(internal) returns void.
  
  SELECT p1.amprocfamily, p1.amprocnum,
  	p2.oid, p2.proname,
*************** SELECT p1.amprocfamily, p1.amprocnum,
*** 998,1009 ****
  FROM pg_amproc AS p1, pg_proc AS p2, pg_opfamily AS p3
  WHERE p3.opfmethod = (SELECT oid FROM pg_am WHERE amname = 'btree')
      AND p1.amprocfamily = p3.oid AND p1.amproc = p2.oid AND
!     (amprocnum != 1
!      OR proretset
!      OR prorettype != 'int4'::regtype
!      OR pronargs != 2
!      OR proargtypes[0] != amproclefttype
!      OR proargtypes[1] != amprocrighttype);
  
  -- For hash we can also do a little better: the support routines must be
  -- of the form hash(lefttype) returns int4.  There are several cases where
--- 991,1004 ----
  FROM pg_amproc AS p1, pg_proc AS p2, pg_opfamily AS p3
  WHERE p3.opfmethod = (SELECT oid FROM pg_am WHERE amname = 'btree')
      AND p1.amprocfamily = p3.oid AND p1.amproc = p2.oid AND
!     (CASE WHEN amprocnum = 1
!           THEN prorettype != 'int4'::regtype OR proretset OR pronargs != 2
!                OR proargtypes[0] != amproclefttype
!                OR proargtypes[1] != amprocrighttype
!           WHEN amprocnum = 2
!           THEN prorettype != 'void'::regtype OR proretset OR pronargs != 1
!                OR proargtypes[0] != 'internal'::regtype
!           ELSE true END);
  
  -- For hash we can also do a little better: the support routines must be
  -- of the form hash(lefttype) returns int4.  There are several cases where
