sort-support-2.patch

text/x-patch

Filename: sort-support-2.patch
Type: text/x-patch
Part: 0
Message: Re: Inlining comparators as a performance optimisation

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
File+
doc/src/sgml/xindex.sgml 0 0
src/backend/access/nbtree/nbtcompare.c 0 0
src/backend/utils/cache/lsyscache.c 0 0
src/backend/utils/sort/tuplesort.c 0 0
src/include/access/nbtree.h 0 0
src/include/catalog/pg_am.h 0 0
src/include/catalog/pg_amproc.h 0 0
src/include/catalog/pg_proc.h 0 0
src/include/utils/builtins.h 0 0
src/include/utils/lsyscache.h 0 0
src/include/utils/sortsupport.h 0 0
src/test/regress/expected/opr_sanity.out 0 0
src/test/regress/sql/opr_sanity.sql 0 0
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..62d03f96de07cbbe14dedefb1596220af398caea 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, SortSupportInfo sinfo)
+ {
+ 	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)
+ {
+ 	SortSupportInfo	sinfo = (SortSupportInfo) PG_GETARG_POINTER(0);
+ 
+ 	sinfo->comparator = btint4fastcmp;
+ 	PG_RETURN_VOID();
+ }
+ 
  Datum
  btint8cmp(PG_FUNCTION_ARGS)
  {
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index cb341b8db679382c3ab8de31ff7a0bb57bc8dc50..cb6a38bcfd29fb17fc62040eef79d34aee9fe802 100644
*** a/src/backend/utils/cache/lsyscache.c
--- b/src/backend/utils/cache/lsyscache.c
*************** get_compare_function_for_ordering_op(Oid
*** 287,292 ****
--- 287,348 ----
  }
  
  /*
+  * 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;
+ 	int16		strategy;
+ 
+ 	/* Find the operator in pg_amop */
+ 	if (get_ordering_op_properties(opno,
+ 								   &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
+ 		{
+ 			/* datatype 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;
+ }
+ 
+ /*
   * get_equality_op_for_ordering_op
   *		Get the OID of the datatype-specific btree equality operator
   *		associated with an ordering operator (a "<" or ">" operator).
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 3505236e5d3b9c753fed11822e7d4b147b0db0eb..179d607a2e959bf77f79fba85146551595f82ef5 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"
  
  
*************** typedef struct
*** 164,169 ****
--- 165,189 ----
  	int			tupindex;		/* see notes above */
  } SortTuple;
  
+ /* 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 */
+ } SortShimInfo;
+ 
+ /*
+  * Information we need for each sort key (column to be sorted).
+  */
+ typedef struct SortKeyData
+ {
+ 	SortSupportInfoData sinfo;	/* sort comparison function */
+ 	AttrNumber	attno;			/* table or index column number to sort */
+ 	bool		reverse;		/* descending-order sort? */
+ 	bool		nulls_first;	/* sort nulls first? */
+ } SortKeyData;
+ 
+ typedef SortKeyData *SortKey;
  
  /*
   * Possible states of a Tuplesort object.  These denote the states that
*************** 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
--- 359,365 ----
  	 * tuplesort_begin_heap and used only by the MinimalTuple routines.
  	 */
  	TupleDesc	tupDesc;
! 	SortKey		sortKeys;		/* array of length nKeys */
  
  	/*
  	 * These variables are specific to the CLUSTER case; they are set by
*************** static void tuplesort_heap_insert(Tuples
*** 457,462 ****
--- 477,484 ----
  static void tuplesort_heap_siftup(Tuplesortstate *state, bool checkIndex);
  static unsigned int getlen(Tuplesortstate *state, int tapenum, bool eofOK);
  static void markrunend(Tuplesortstate *state, int tapenum);
+ static void PrepareSortSupportComparisonShim(SortSupportInfo sinfo,
+ 											 Oid cmpFunc);
  static int comparetup_heap(const SortTuple *a, const SortTuple *b,
  				Tuplesortstate *state);
  static void copytup_heap(Tuplesortstate *state, SortTuple *stup, void *tup);
*************** 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);
--- 635,674 ----
  	state->reversedirection = reversedirection_heap;
  
  	state->tupDesc = tupDesc;	/* assume we need not copy tupDesc */
! 	state->sortKeys = (SortKey) palloc0(nkeys * sizeof(SortKeyData));
  
  	for (i = 0; i < nkeys; i++)
  	{
+ 		SortKey		sortKey = state->sortKeys + i;
  		Oid			sortFunction;
! 		bool		issupport;
  
  		AssertArg(attNums[i] != 0);
  		AssertArg(sortOperators[i] != 0);
  
! 		if (!get_sort_function_for_ordering_op(sortOperators[i],
! 											   &sortFunction,
! 											   &issupport,
! 											   &sortKey->reverse))
  			elog(ERROR, "operator %u is not a valid ordering operator",
  				 sortOperators[i]);
  
! 		sortKey->sinfo.sinfo_collation = sortCollations[i];
! 		sortKey->sinfo.sinfo_cxt = CurrentMemoryContext;
! 		sortKey->attno = attNums[i];
! 		sortKey->nulls_first = nullsFirstFlags[i];
  
! 		if (issupport)
! 		{
! 			/* The sort support function should provide a comparator */
! 			OidFunctionCall1(sortFunction, PointerGetDatum(&sortKey->sinfo));
! 			Assert(sortKey->sinfo.comparator != NULL);
! 		}
! 		else
! 		{
! 			/* We'll use a shim to call the old-style btree comparator */
! 			PrepareSortSupportComparisonShim(&sortKey->sinfo, sortFunction);
! 		}
  	}
  
  	MemoryContextSwitchTo(oldcontext);
*************** ApplySortFunction(FmgrInfo *sortFunction
*** 2707,2712 ****
--- 2728,2824 ----
  								   datum2, isNull2);
  }
  
+ /*
+  * 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, SortSupportInfo sinfo)
+ {
+ 	SortShimInfo *extra = (SortShimInfo *) sinfo->sinfo_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.
+  */
+ static void
+ PrepareSortSupportComparisonShim(SortSupportInfo sinfo, Oid cmpFunc)
+ {
+ 	SortShimInfo   *extra;
+ 
+ 	extra = (SortShimInfo *) MemoryContextAlloc(sinfo->sinfo_cxt,
+ 												sizeof(SortShimInfo));
+ 
+ 	/* Lookup the comparison function */
+ 	fmgr_info_cxt(cmpFunc, &extra->flinfo, sinfo->sinfo_cxt);
+ 
+ 	/* We can initialize the callinfo just once and re-use it */
+ 	InitFunctionCallInfoData(extra->fcinfo, &extra->flinfo, 2,
+ 							 sinfo->sinfo_collation, NULL, NULL);
+ 	extra->fcinfo.argnull[0] = false;
+ 	extra->fcinfo.argnull[1] = false;
+ 
+ 	sinfo->sinfo_extra = extra;
+ 	sinfo->comparator = comparison_shim;
+ }
+ 
+ /*
+  * 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
+ inlineApplyComparator(SortKey sortKey,
+ 					  Datum datum1, bool isNull1,
+ 					  Datum datum2, bool isNull2)
+ {
+ 	int			compare;
+ 
+ 	if (isNull1)
+ 	{
+ 		if (isNull2)
+ 			compare = 0;		/* NULL "=" NULL */
+ 		else if (sortKey->nulls_first)
+ 			compare = -1;		/* NULL "<" NOT_NULL */
+ 		else
+ 			compare = 1;		/* NULL ">" NOT_NULL */
+ 	}
+ 	else if (isNull2)
+ 	{
+ 		if (sortKey->nulls_first)
+ 			compare = 1;		/* NOT_NULL ">" NULL */
+ 		else
+ 			compare = -1;		/* NOT_NULL "<" NULL */
+ 	}
+ 	else
+ 	{
+ 		compare = (*sortKey->sinfo.comparator) (datum1, datum2,
+ 												&sortKey->sinfo);
+ 		if (sortKey->reverse)
+ 			compare = -compare;
+ 	}
+ 
+ 	return compare;
+ }
+ 
  
  /*
   * Routines specialized for HeapTuple (actually MinimalTuple) case
*************** 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;
--- 2827,2833 ----
  static int
  comparetup_heap(const SortTuple *a, const SortTuple *b, Tuplesortstate *state)
  {
! 	SortKey		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;
  
--- 2838,2846 ----
  	CHECK_FOR_INTERRUPTS();
  
  	/* Compare the leading sort key */
! 	compare = inlineApplyComparator(sortKey,
! 									a->datum1, a->isnull1,
! 									b->datum1, b->isnull1);
  	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,
--- 2850,2859 ----
  	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->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;
  	}
--- 2862,2870 ----
  		datum1 = heap_getattr(&ltup, attno, tupDesc, &isnull1);
  		datum2 = heap_getattr(&rtup, attno, tupDesc, &isnull2);
  
! 		compare = inlineApplyComparator(sortKey,
! 										datum1, isnull1,
! 										datum2, isnull2);
  		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);
  }
--- 2891,2897 ----
  	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].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);
  }
--- 2943,2949 ----
  	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].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);
  	}
  }
  
--- 2951,2963 ----
  static void
  reversedirection_heap(Tuplesortstate *state)
  {
! 	SortKey		sortKey = state->sortKeys;
  	int			nkey;
  
! 	for (nkey = 0; nkey < state->nKeys; nkey++, sortKey++)
  	{
! 		sortKey->reverse = !sortKey->reverse;
! 		sortKey->nulls_first = !sortKey->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/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..f5e8e25a38e159ea7364d66917d724771aef02f4 100644
*** a/src/include/utils/lsyscache.h
--- b/src/include/utils/lsyscache.h
*************** extern bool get_ordering_op_properties(O
*** 50,55 ****
--- 50,57 ----
  						   Oid *opfamily, Oid *opcintype, int16 *strategy);
  extern bool get_compare_function_for_ordering_op(Oid opno,
  									 Oid *cmpfunc, bool *reverse);
+ 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 ...7e196a413653fa0de3f10b246078e01be46453a3 .
*** a/src/include/utils/sortsupport.h
--- b/src/include/utils/sortsupport.h
***************
*** 0 ****
--- 1,87 ----
+ /*-------------------------------------------------------------------------
+  *
+  * 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 SortSupportInfoData 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
+  * SortSupportInfoData struct when called, so they can use it to store
+  * additional private data as needed.  In particular, for collation-aware
+  * datatypes, the sinfo_collation field is set before calling BTSORTSUPPORT
+  * and is available to all support functions.  Additional opclass-dependent
+  * data can be stored using the sinfo_extra field.  Any such data
+  * should be allocated in the sinfo_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
+ 
+ typedef struct SortSupportInfoData *SortSupportInfo;
+ 
+ typedef struct SortSupportInfoData
+ {
+ 	/*
+ 	 * These fields are initialized before calling the BTSORTSUPPORT function
+ 	 * and should not be changed later.
+ 	 */
+ 	Oid			sinfo_collation;		/* Collation to use, or InvalidOid */
+ 	MemoryContext	sinfo_cxt;			/* Context containing sort info */
+ 
+ 	/*
+ 	 * sinfo_extra is zeroed before calling the BTSORTSUPPORT function, and
+ 	 * is not touched subsequently by callers.
+ 	 */
+ 	void	   *sinfo_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, SortSupportInfo sinfo);
+ 
+ 	/*
+ 	 * Additional sort-acceleration functions might be added here later.
+ 	 */
+ } SortSupportInfoData;
+ 
+ #endif   /* SORTSUPPORT_H */
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